+1

Giới thiệu graphql và cách tương tác graphql với koajs

Hôm nay mình xin giới thiệu 1 chút về graphql và cách tương tác với một server nodejs dùng koajs.

Giới thiệu

GraphQL là một ngôn ngữ query cho API dùng để viết các câu API một cách uyển chuyển, chính xác những gì cần có.

Các khái niệm trong graphql

  1. Fields: Là một kiểu xác định các thuộc tính cho object. Ví dụ ở đây ta có field name.
{
  hero {
    name
  }
}
  1. Arguments Đơn giản là param cần push lên server để get chính xác record mong muốn theo các điều kiện. Ví dụ, ở đây ta có biến id:
{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
  1. Aliases Là cách đặt tên khác cho kết quả trả về:
{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
Kết quả là:
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}
  1. Variables Giống như parameter, là một định dạng dynamic cho biến:
query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
Với biến:
{
  "episode": "JEDI"
}
Sẽ ra kết quả:
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

Và dĩ nhiên chúng ta cũng có thể định dạng loại biến, như là loại Episode ở đây:

query HeroNameAndFriends($episode: Episode = "JEDI") {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
  1. Directives Là cách chúng ta dùng biến để change cấu trúc của query một cách tự động: Ví dụ:
query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}
Với biến:
{
  "episode": "JEDI",
  "withFriends": false
}
Chúng ta sẽ quyết định kết quả trả về có include thêm friends không:
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}
  1. Mutations Là cách thay đổi data trên server, dùng như việc create và update trong REST.
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

Với biến:

{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}

Ta sẽ có kết quả:
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

Tương tác graphql với koajs

Hiện tại trong Koajs có một package rất phù hợp để cấu hình server graphql là koa-graphql.

  • Lắp đặt: npm install --save koa-graphql
  • Tương tác trực tiếp với Koa thông qua koa-router:
const Koa = require('koa');
const Router = require('koa-router'); // koa-router@7.x
const graphqlHTTP = require('koa-graphql');

const app = new Koa();
const router = new Router();

router.all('/graphql', graphqlHTTP({
  schema: MyGraphQLSchema,
  graphiql: true
}));

app.use(router.routes()).use(router.allowedMethods());

Cài đặt một chức năng đơn giản là load project về client với query dựa trên graphql:

  1. Database:
  • Bảng projects gồm: id | name | description | logo | num_star | owner_id | is_pinned
  • Bảng users gồm: id | fullname | username | email | avatar.
  1. Define type object tương tác là Project hay User:
import {
  GraphQLBoolean,
  GraphQLEnumType,
  GraphQLFloat,
  GraphQLInt,
  GraphQLList,
  GraphQLObjectType,
  GraphQLString,
  GraphQLInterfaceType
} from 'graphql';

export const UserType = new GraphQLObjectType({
  name: 'User',
  description: 'This represents a User',
  fields: () => {
    return {
      id: {
        type: GraphQLInt,
        resolve(user) {
          return user.id;
        }
      },
      fullname: {
        type: GraphQLString,
        resolve(user) {
          return user.fullname;
        }
      },
      username: {
        type: GraphQLString,
        resolve(user) {
          return user.username;
        }
      },
      email: {
        type: GraphQLString,
        resolve(user) {
          return user.email;
        }
      },
      avatar: {
        type: GraphQLString,
        resolve(user) {
          return user.avatar;
        }
      },
    }
  }
});

export const ProjectType = new GraphQLObjectType({
  name: 'Project',
  description: 'This represents a Project',
  fields: () => {
    return {
      id: {
        type: GraphQLInt,
        resolve(project) {
          return project.id;
        }
      },
      name: {
        type: GraphQLString,
        resolve(project) {
          return project.name;
        }
      },
      description: {
        type: GraphQLString,
        resolve(project) {
          return project.description;
        }
      },
      logo: {
        type: GraphQLString,
        resolve(project) {
          return project.logo;
        }
      },
      num_star: {
        type: GraphQLInt,
        resolve(project) {
          return project.num_star;
        }
      },
      user: {
        type: UserType,
        resolve(project) {
          return project.getUser();
        }
      },
    }
  }
});
  1. Viết query get list project có tương tác phân trang:
import {
  GraphQLBoolean,
  GraphQLEnumType,
  GraphQLFloat,
  GraphQLInt,
  GraphQLList,
  GraphQLObjectType,
  GraphQLString,
  GraphQLNonNull,
  GraphQLInterfaceType
} from 'graphql';

import models from '../../models';

import {
  ProjectType
} from './types';

const queries = {
  projectsHomePage: {
    type: new GraphQLList(ProjectType),
    args: {
      offset: {
        type: new GraphQLNonNull(GraphQLInt)
      },
      limit: {
        type: new GraphQLNonNull(GraphQLInt)
      }
    },
    resolve(_, args) {
      return models.Project.findAll({
        offset: args.offset,
        limit: args.limit,
      })
    }
  },
};

export default queries;

  1. Ta dùng query để lấy kết quả, nhưng sẽ dùng mutation để thay đổi update data:
  • Để làm được vậy, ta define loại input cần đưa vào:
import {
  GraphQLInt,
  GraphQLFloat,
  GraphQLList,
  GraphQLInputObjectType,
  GraphQLNonNull,
  GraphQLString,
  GraphQLScalarType,
  GraphQLError
} from 'graphql';

export const ProjectInputType = new GraphQLInputObjectType({
  name: 'ProjectInputType',
  description: 'Project type',
  fields: () => ({
    name: { type: new GraphQLNonNull(GraphQLString)},
    description: { type: new GraphQLNonNull(GraphQLString)},
    logo: { type: new GraphQLNonNull(GraphQLString)},
    num_star: { type: new GraphQLNonNull(GraphQLInt)},
    owner_id: { type: new GraphQLNonNull(GraphQLInt)},
    users_id: { type: new GraphQLList(GraphQLString)},
  })
});

  • Đây là ví dụ dùng mutation để tạo thêm project (ở đây để tương tác database tôi dùng sequelize, các bạn không cần để ý kĩ code create đâu ạ 😄):
import models from '../../models';
import async from 'async';
import Sequelize from 'sequelize';
const Op = Sequelize.Op;

import {
  GraphQLNonNull,
  GraphQLString,
  GraphQLInt
} from 'graphql';

import {ProjectType} from './types';
import {ProjectInputType} from './inputTypes';

const mutations = {
  createProject: {
    type: ProjectType,
    args: {
      project: { type: new GraphQLNonNull(ProjectInputType) }
    },
    resolve(_, args) {
      const projectData = {
        ...args.project,
      };
      return new Promise((resolve, reject) => {
        async.auto({
          create_project: function(callback) {
            models.Project.create(projectData)
            .then(g => {
              callback(null, g);
              return g;
            });
          },
          find_user_id: ['create_project', function(projectSaved, callback) {
            async.map(projectData.users_id,
              function(value, callback1) {
                models.User.findAll({
                  where: {
                    [Op.or]: [{username: value}, {email: value}],  // (a = 5 OR a = 6)
                  }
                }).then((users) => {
                  callback1(null, users[0].id);
                })
              }, function(err, results) {
                results.push(projectSaved.owner_id);
                callback(null, results);
            });
          }],
          link_project_user: ['create_project', 'find_user_id', function(results, callback) {
            async.map(results.find_user_id,
              function(value, callback1) {
                let role = (value == results.create_project.owner_id) ? 'admin':'guest';
                models.ProjectUser.create({
                  user_id: value,
                  project_id: results.create_project.id,
                  role: role
                }).then((projectUser) => {
                  callback1(null, projectUser);
                })
              }, function(err, results) {
                callback(null, results);
            });
          }],
        }, function(err, results) {
            if (err) {
              reject(err);
            } else {
              resolve(results.create_project);
            }
        });
      });
    }
  },
}

export default mutations;
  1. Tương tác ở client để gọi thực thi câu query:
  • Ở client tôi sử dụng vuejs và vue-apollo để gọi thực thi: Đơn giản chỉ cần tích hợp vue-apollo theo guide, sau đó muốn query ta gọi:
  Khai báo query:
  import gql from 'graphql-tag';
// GraphQL query
const allProjectsQuery = gql
  query projects($offset: Int!, $limit: Int!) {
    projectsHomePage(offset: $offset, limit: $limit) {
      id
      name
      description
      logo
      num_star
      user {
        id
        fullname
        username
        avatar
      }
    }
  }
  
  Lúc nào cần gọi query thì:
  self.$apollo.query({
        // Query
        query: allProjectsQuery,
        // Parameters
        variables: {
          offset: self.offset,
          limit: self.limit
        },
      }).then((results) => {
        if (results.data.projectsHomePage.length != 0) {
          self.projects = self.projects.concat(results.data.projectsHomePage);
        } else {
          self.isLast = true;
        }
        self.loading = false;

      }).catch((error) => {
        // Error
        console.error(error)
      });
;

Kết luận:

Theo mình thấy, graphql chỉ nên dùng khi các thành phần data cấu trúc phức tạp, cần nhiều loại dữ liệu và điều kiện truy vấn khác nhau, đặc biệt là khi cần nhiều loại API layers cho các client khác nhau, vì trong GraphQL chúng ta có thể vừa get, update từ nhiều loại resource chỉ trong 1 cuộc gọi, có như vậy mình mới tận dụng được tính flexiable của nó, còn lại cứ REST cho nó bền 😄.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí