Graphql
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
Contents
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 } }