大部分代碼仍是來自sofa 的官方文檔,同時添加了docker && docker-compose集成
備註: 代碼使用typescript 同時運行的時候爲了方便直接運行使用ts-node 運行node
version: "3" services: api: build: ./ image: dalongrong/sofa-graphql2rest ports: - "4000:4000"
FROM node:alpine WORKDIR /server COPY . /server RUN yarn install CMD ["yarn", "start"]
{ "name": "sofa-graphql2rest", "version": "1.0.0", "license": "MIT", "main": "server.js", "scripts": { "start": "ts-node index.ts" }, "dependencies": { "body-parser": "1.18.3", "chalk": "2.4.2", "express": "4.16.4", "express-graphql": "0.7.1", "graphql": "^14.0.2", "graphql-subscriptions": "^1.0.0", "graphql-tag": "2.10.0", "graphql-tools": "4.0.3", "sofa-api": "^0.2.2", "swagger-ui-express": "4.0.2" }, "devDependencies": { "@types/swagger-ui-express": "3.0.0", "@types/body-parser": "1.17.0", "@types/express": "4.16.0", "@types/express-graphql": "0.6.2", "@types/graphql": "14.0.3", "esm": "^3.0.84", "ts-loader": "^5.3.3", "ts-node": "7.0.1", "typescript": "3.2.2" } }
import gql from 'graphql-tag'; export const typeDefs = gql` type Pizza { dough: String! toppings: [String!] } type Salad { ingredients: [String!]! } union Food = Pizza | Salad type Book { id: ID! title: String! } type User { id: ID! name: String! favoritePizza: Pizza! favoriteBook: Book! favoriteFood: Food! shelf: [Book!]! } type Post { comments(filter: String!): [String!] } type Query { me: User user(id: ID!): User users: [User!] usersLimit(limit: Int!): [User!] usersSort(sort: Boolean!): [User!] book(id: ID!): Book books: [Book!] never: String feed: [Post] } type Mutation { addBook(title: String!): Book } type Subscription { onBook: Book } schema { query: Query mutation: Mutation subscription: Subscription } `;
resolvers 定義 resolvers.ts 同時集成了subscriptiongit
import { PubSub } from 'graphql-subscriptions'; const pubsub = new PubSub(); import { UsersCollection, BooksCollection, PostsCollection, } from './collections'; const BOOK_ADDED = 'BOOK_ADDED'; export const resolvers: any = { Query: { me() { return UsersCollection.get(1); }, user(_: any, { id }: any) { return UsersCollection.get(id); }, users() { return UsersCollection.all(); }, usersLimit(_: any, { limit }: any) { return UsersCollection.all().slice(0, limit); }, usersSort(_: any, { sort }: any) { const users = UsersCollection.all(); return sort ? users.sort((a, b) => b.id - a.id) : users; }, book(_: any, { id }: any) { return BooksCollection.get(id); }, books() { return BooksCollection.all(); }, feed() { return PostsCollection.all(); }, }, Mutation: { addBook(_: any, { title }: any) { const book = BooksCollection.add(title); pubsub.publish(BOOK_ADDED, { onBook: book }); return book; }, }, Subscription: { onBook: { subscribe: () => pubsub.asyncIterator([BOOK_ADDED]), }, }, Food: { __resolveType(obj: any) { if (obj.ingredients) { return 'Salad'; } if (obj.toppings) { return 'Pizza'; } return null; }, }, Post: { comments(post: { comments: string[] }, { filter }: { filter: string }) { return post.comments.filter( comment => !filter || comment.toLowerCase().indexOf(filter.toLowerCase()) > -1 ); }, }, };
代碼入口
index.tsgithub
import * as express from 'express'; import { makeExecutableSchema } from 'graphql-tools'; import * as bodyParser from 'body-parser'; import * as useGraphQL from 'express-graphql'; import * as swaggerUi from 'swagger-ui-express'; import chalk from 'chalk'; import { resolve } from 'path'; import { typeDefs } from './types'; import { resolvers } from './resolvers'; import * as swaggerDocument from './swagger.json'; // Sofa import sofa, { OpenAPI } from 'sofa-api'; import { logger } from 'sofa-api/dist/logger'; const app = express(); app.use(bodyParser.json()); const schema = makeExecutableSchema({ typeDefs, resolvers: resolvers as any, }); const openApi = OpenAPI({ schema, info: { title: 'Example API', version: '3.0.0', }, }); app.use( sofa({ schema, ignore: ['User.favoriteBook'], onRoute(info) { openApi.addRoute(info, { basePath: '', }); }, }) ); openApi.save(resolve(__dirname, './swagger.json')); app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); app.use('/webhook', (req, res) => { logger.info('Received a webhook', req.body); res.statusCode = 200; res.statusMessage = 'OK'; res.send(); }); app.use( '/graphql', useGraphQL({ schema, graphiql: true, }) ); const port = 4000; app.listen(port, () => { const url = `http://localhost:${4000}`; function printUrl(path: string) { return chalk.gray(url + path); } console.log(` ${chalk.bold('GraphQL:')} ${printUrl('/graphql')} ${chalk.bold('Queries:')} me: ${printUrl('/me')} users: ${printUrl('/users')} user: ${printUrl('/user/1')} books: ${printUrl('/books')} book: ${printUrl('/book/1')} ${chalk.bold('Mutations:')} addBook: ${printUrl('/add-book')} ${chalk.italic.gray( 'POST: {title}' )} `); });
docker-compose build && docker-compose up -d
http://localhost:4000/docs
graphql ui graphiql 工具web
http://localhost:4000/graphql
sofa 在設計上充分graphql 的ast 語法,同時集成了open api ,同時按照設計的方案,能夠集成比較多的graphql 的平臺,主要是scemadocker
https://github.com/rongfengliang/sofa-graphql2rest-docker-compose
https://sofa-api.com/
https://github.com/Urigo/sofatypescript