zoukankan      html  css  js  c++  java
  • sofa graphql 2 rest api 试用

    大部分代码还是来自sofa 的官方文档,同时添加了docker && docker-compose集成
    备注: 代码使用typescript 同时运行的时候为了方便直接运行使用ts-node 运行

    环境准备

    • docker-compose 文件
    version: "3"
    services:
      api:
          build: ./
          image: dalongrong/sofa-graphql2rest
          ports:
          - "4000:4000"
    • dockerfile
    FROM node:alpine
    WORKDIR /server
    COPY . /server
    RUN yarn install
    CMD ["yarn", "start"]

    代码说明

    • package.json 依赖包
    {
      "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"
      }
    }
    
    • graphql api 开发
      基于graphql-tools,同时集成express-graphql 以及swagger open api 文档
      graphql schema 定义 type.ts
    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 同时集成了subscription

    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.ts

    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
    • 效果
      openapi docs
    http://localhost:4000/docs


    graphql ui graphiql 工具

    http://localhost:4000/graphql

    • 查询执行
      rest api

      graphql api

    说明

    sofa 在设计上充分graphql 的ast 语法,同时集成了open api ,同时按照设计的方案,可以集成比较多的graphql 的平台,主要是scema

    参考资料

    https://github.com/rongfengliang/sofa-graphql2rest-docker-compose
    https://sofa-api.com/
    https://github.com/Urigo/sofa

  • 相关阅读:
    P4374 [USACO18OPEN]Disruption P
    POJ
    Git
    SpringBoot集成RabbitMQ
    GIS类型文件剖析
    SpringBoot全局异常处理
    SpringCloud Feign异常处理
    SpringBoot注解
    Restful风格接口定义
    LOD技术的理解
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/10326302.html
Copyright © 2011-2022 走看看