Thumbnail
Category: Lập trình

Graphql

Date: September 17, 2021
42 views

Grapql cách viết api. Cách viết do Facebook lập ra, để gom mọi api về 1 api. Api này có khả năng:

  • Tránh dư thừa dữ liệu
  • Dễ quản lý
  • Có khả năng realtime

1. Anh Tân chia sẻ

2. Thử Graphql với laravel

2.1 Thư viện rebing/graphql-laravel

Cá nhân mình thấy thư viện này dễ hiểu, dễ cấu hình. Tuy nhiên, việc viết code định graphql viết theo một chuẩn mới khá khó viết so với cấu trúc mặc định của graphql.

Tham khảo: https://www.twilio.com/blog/build-graphql-powered-api-laravel-php

Lưu ý: Khi thực hiện code link trên nhớ thay App\Book =>> App\Models\Book. Vì Laravel mới đã chuyển model và folder Models.

2.1 Thư viện lighthouse


Mình thấy thư viện này dùng khá phổ biến. Do cấu trúc code giống cấu trúc của graphql.

Tham khảo: https://www.toptal.com/graphql/laravel-graphql-server-tutorial

3. Kinh nghiệm

3.1 Phần mềm test graphql

Phần mềm tên là GraphiQL khá dễ sử dụng.

Link download: https://www.electronjs.org/apps/graphiql

3.2 Cài đặt light-house laravel


composer require nuwave/lighthouse


php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider"

Sau đó sẽ có file config/lighthouse.php để cấu hình cho light-house gồm route,…


'schema' => [
    'register' => base_path('graphql/schema.graphql'),
    // nơi đăng ký file định nghĩa graphql
],

Chạy test thử:


php artisan serve
​
// Vào phần mềm graphiQL mục 3.1 để chạy
http://localhost:8000/graphql

3.3 light house library

Schema có nhiều type. Có 3 loại mặc định:

  • Query: lấy dữ liệu
  • Mutation: thay đổi dữ liệu
  • Subscription: realtime

3.3.1 Type

  • Dạng object
  • Scalar: Kiểu dữ liệu
  • Enum
  • Input: Data từ request truyền vào (parameter)
  • Interface
  • Union

3.3.2 Simple

Định nghĩa ở graphql/schema.graphql


type Query {
  hello: String!
}

Tạo query file


php artisan lighthouse:query Hello

Sẽ sinh ra file tương ứng App\GraphQL\Queries. Function __invoke() sẽ xử lý và trả data về


<?php
​
namespace App\GraphQL\Queries;
​
class Hello
{
    public function __invoke(): string
    {
        return 'world!';
    }
}

Như vậy, khi gọi


{
  hello
}
​
// result 
{
  "data": {
    "hello": "world!"
  }
}

3.3.3 Có argument


type Query {
  greet(name: String!): String
  // greet(name: String = "you"): String  => set param mặc định khi không truyển vào
                                            // param không bắt buộc
}


<?php
​
namespace App\GraphQL\Queries;
​
class Greet
{
    public function __invoke($rootValue, array $args): string
    {
        return "Hello, {$args['name']}!";
    }
}


// gọi api
{
  greet(name: "Foo")
}
​
// result
{
  "data": {
    "greet": "Hello, Foo!"
  }
}

3.3.4 directive


@all
@paginate
@eq
@orderBy 
@first
@create
@update
@upsert // update or create if not exist
@delete
// relationship
@hasOne
@hasMany
@belongsTo
@belongsToMany
@morphOne
@morphTo
@morphMany
3.3.4.1 Sử dụng local scope

Eloquent


use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
​
class User extends Model
{
    public function scopeVerified(Builder $query): Builder
    {
        return $query->whereNotNull('email_verified_at');
    }
}


type Query {
  users: [User]! @all(scopes: ["verified"])
}

3.4 Vue apollo

3.4.1 Cài đặt


npm install vue-apollo@3.0.3 graphql@15.2.0 apollo-boost@0.4.9

3.4.2 Cấu hình

Add apollo Client in app.js


import ApolloClient from 'apollo-boost'
​
const apolloClient = new ApolloClient({
  // You should use an absolute URL here
  uri: 'http://127.0.0.1:8000/graphql'
})

Import VueApollo


import VueApollo from 'vue-apollo'
  
Vue.use(VueApollo)

Add apolloProvider


const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
  })

Add apoloProvider to app Vue


const app = new Vue({
    el: '#app',
    router,
    apolloProvider
});

3.4.3 Sử dụng

Query
Simple query


<script>
import gql from 'graphql-tag'
​
export default {
    apollo: {
        // Simple query that will update the 'hello' vue property
        posts: gql`{
            posts {
                id
                title
            }
        }`,
    },
}
</script>
Param query

Trong một query sẽ gồm 2 phần

  • query
  • variables: sẽ return về các biến dynamic


<script>
import gql from 'graphql-tag'
​
export default {
    apollo: {
        post: {
            query: gql`
                query ($id: ID!) {
                    post(id: $id) {
                        id
                        title
                        content
                        author {
                            id
                            name
                            avatar
                        }
                        topic {
                            name
                            slug
                        }
                    }
                }`,
            variables() {
                return {
                    id: this.$route.params.id
                }
            }
        }
    }
}
</script>
Loading state


// check loading
$apollo.loading
  
// Check a query loading
$apollo.queries.posts.loading
  
//example
<template>
    <div>
        List Post
        <div>Loading: {{ $apollo.loading }}</div>
        <div>Is posts loading: {{ $apollo.queries.posts.loading }}</div>
    </div>
</template>
Xử lý lỗi


<script>
import gql from 'graphql-tag'
​
export default {
    apollo: {
        post: {
            query: gql`
                ...`,
            variables() {
                return {
                    id: this.$route.params.id
                }
            },
            error() {
                this.$router.push({ name: '404' });
            }
        }
    }
}
</script>
Mutation


import CardAdd from '../graphql/CardAdd.gql'
​
this.$apollo.mutate({
  mutation: CardAdd,
  variables: {
    title: 'Added through mutation',
    list_id: 1,
    order: 1
    }
});
​
​
// Có update
addCard() {
  const self = this;
  this.$apollo.mutate({
    mutation: CardAdd,
    variables: {
      title: this.title,
      list_id: this.list.id,
      order: 1
      },
    update(store, {data: {cardAdd} }) {
      const data = store.readQuery({
        query: BoardQuery,
        variables: { id: Number(self.list.board_id)}
      });
​
      data.board.lists.find(list => (list.id == self.list.id)).cards.push(cardAdd);
​
      store.writeQuery({ query: BoardQuery, data });
    }
  });
  this.closed();
},

3.4.4 Tách query apollo ra thành file .gql

Tạo file xxx.gql, lấy đoạn query sang file này

Đặt tên file vào sau query.

Ví dụ: tên file là: BoardWithListsAndCards.gql thì query BoardWithListsAndCards( …


query BoardWithListsAndCards($id: ID!) {
    board(id: $id) {
        title
        color
        lists {
            id
            title
            cards {
                id
                title
            }
        }
    }
}

Import vào component


import BoardQuery from './graphql/BoardWithListsAndCards.gql' // dòng này
​
export default {
    apollo: {
        board: {
            query: BoardQuery, // dòng này
            variables() {
                return {
                    id: 1
                }
            },
            error() {
                this.$router.push({ name: '404' });
            }
        }
    }
}

Cấu hình lại webpack mix laravel


const mix = require('laravel-mix');
​
/*============================================
* Thêm đoạn này
*============================================*/
mix.extend(
  'graphql',
  new class {
      dependencies() {
          return ['graphql', 'graphql-tag']
      }
​
      webpackRules() {
          return {
              test: /\.(graphql|gql)$/,
              exclude: /node_modules/,
              loader: 'graphql-tag/loader'
          }
      }
  }()
);
/*============================================*/
​
 mix.js("resources/js/app.js", "public/js")
 .postCss("resources/css/app.css", "public/css", [
   require("tailwindcss"),
 ]);
​
/*============================================
* và thêm đoạn này
*============================================*/
 mix.graphql();
/*============================================*/

Build lại


npm run watch

3.5 Param query trong graphql


query ($id: ID!) {
     post(id: $id) {
    id
    title
  }
}

3.6 Đồng bộ relationship giữa laravel và graphql-lighthouse

Tên function relationship trong model đặt thế nào thì trong định nghĩa type của graphql cũng sử dụng tên đó định nghĩa, nếu không sẽ bị lỗi:

Call to undefined relationship [lists] on model

Tui mất khá nhiều thời gian vì lỗi này.

4. Các ví dụ làm từ course

4.1 Blog

Repository github: https://github.com/tronghao/learn-graphql-blogql

Cài laravel và các dependences


composer create-project laravel/laravel=7.12.0 blog-ql --prefer-dist
composer require nuwave/lighthouse=4.14.1
composer require mll-lab/laravel-graphql-playground=2.1.0
  
// Nếu laravel version >= 8
composer require --dev barryvdh/laravel-ide-helper
// Nếu laravel version 7
composer require --dev barryvdh/laravel-ide-helper 2.8


composer require laravel/ui
  composer require laravel/ui:^2.4  // với laravel 7
    
php artisan ui vue

Cài nodejs


// check
node -v
npm -v

Run lệnh


npm install && npm run dev

Install tailwindcss


npm install tailwindcss@1.4.6
  
  // delete folder resource/js/sass
  // create file resource/css/app.css
  @tailwind base;
  @tailwind components;
  @tailwind utilities
  
npx tailwindcss init

Chỉnh webpack.mix.js


 mix.js("resources/js/app.js", "public/js")
 .postCss("resources/css/app.css", "public/css", [
   require("tailwindcss"),
 ]);
​
// sau đó
npm run dev

add version to mix


// thêm vào file webpack.mix.js
if (mix.inProduction()) {
     mix.version();
 }

Publish vendor light-housr


php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema

Create migration and model Post and Topic

  • Post: id, title, lead, content.
  • Topic: id, slug, name

Add relationship model

install vue-router

  • mode: history. Cần cấu hình lại router Laravel toàn bộ là dynamic route, còn route của graphql là real route


Route::get('/{any?}', function () {
    return view('welcome');
})->where('any', '^(?!graphql)[\/\w\.-]*');

install vue apollo

install graphql-tag


npm i graphql-tag@2.10.3

intall extension Tailwind css IntelliSense in vs code

install moment.js


npm i moment@2.27.0

Tạo filter vuejs


Vue.filter("timeago", value => moment(value).fromNow() );
Vue.filter("longDate", value => moment(value).format("MMMM Do YYYY") );

Xử lý 404


const routes = [
    { 
        path: '/',
        name: 'index',
        component: PostList 
    },
    {
        path: '/topics/:slug',
        name: 'topic',
        component: TopicPostList,
    },
    { 
        path: '/post/:id', 
        name: 'post',
        component: Post 
    },
    { 
        path: '/authors/:id', 
        name: 'author',
        component: AuthorPostList 
    },
    {
        path: '*',
        name: '404',
        component: {
          template: '<div>Not Found</div>'
        }
    }
  ];


<script>
import gql from 'graphql-tag'
​
export default {
    apollo: {
        post: {
            query: gql`
                ...`,
            variables() {
                return {
                    id: this.$route.params.id
                }
            },
            error() {
                this.$router.push({ name: '404' });
            }
        }
    }
}
</script>

4.2 Trello


composer create-project laravel/laravel=7.12.0 laravello --prefer-dist
composer require nuwave/lighthouse=4.15.0
composer require mll-lab/laravel-graphql-playground=2.3.0
composer require laravel/telescope=3.5.0
composer require --dev barryvdh/laravel-ide-helper 2.8
composer require laravel/ui:^2.4


npm i vue-apollo@3.0.3 graphql@15.2.0 apollo-boost@0.4.9

Mutation


type Mutation {
    cardAdd(title: String!, order: Int, list_id: ID!, owner_id: ID!): Card! @create
}
​
// query
mutation {
  cardAdd(title: "xxx", order: 1, list_id:1, owner_id: 1) {
    id
    title
  }
}

Sử dụng input để gọn hơn.


type Mutation {
    cardAdd(input: CardAddInput! @spread): Card! @create
}
​
​
input CardAddInput {
    title: String!
    order: Int
    list_id: ID!
    owner_id: ID!
}
​
​
// cách query
mutation CardAdd($list_id: ID!, $title: String!, $order: Int!) {
  cardAdd(input: {title: $title, order: $order, list_id:$list_id, owner_id: 1}) {
    id
    title
  }
}

Copyright © 2025 All Right Reserved