Giới thiệu
Ở phần trước, chúng ta đã xây dựng xong phần Authentication cho API hoạt động trên Firebase Cloud Functions, trong phần này, chúng ta sẽ tiếp tục xây dựng API theo GraphQL và Firebase Realtime DB
Stack sử dụng lần này:
- Authentication Module có được từ phần trước
- Firebase Cloud Functions: Serverless (FaaS) Platform
- Firebase Realtime Database
- Express: Nodejs framework (router, middleware)
- Apollo Server: GraphQL server tools
Tìm hiểu và cài đặt
GraphQL
Để bắt đầu, chúng ta sẽ tìm hiểu xem GraphQL là cái quần què gì trước đã phải không các bạn?
Ở trên viblo đã có nhiều bài giải thích rất cụ thể và dễ hiểu rồi, nên mình sẽ giải thích một cách ngắn gọn trong phạm vi bài viết này thôi.
Các bạn có thể đọc bài viết này để hiểu rõ hơn về GraphQL nhé
Mình sẽ tóm tắt lại bằng cách trích dẫn những ý chính của bài viết Cùng tìm hiểu về GraphQL
Vậy GraphQL là gì?
GraphQL là một Graph Query Language được dành cho API. Nó được phát triển bởi Facebook. GraphQL từ khi ra đời đã gần như thay thế hoàn toàn REST bởi sự hiệu quả, mạnh mẽ và linh hoạt hơn rất nhiều.
GraphQL khác REST ở chỗ nào?
Vấn đề mà REST đang gặp phải là nó việc phản hồi dữ liệu của REST trả về quá nhiều hoặc là quá ít. Trong cả 2 trường hợp thì hiệu suất của ứng dụng đều bị ảnh hưởng khá nhiều. Giải pháp mà GraphQL đưa ra là cho phép khai báo dữ liệu nơi mà một client có thể xác định chính xác dữ liệu mà mình cần từ một API.
Hơn nữa, thay vì có nhiều endpoint như REST, GraphQL chỉ có duy nhất một endpoint. Hai phía Client và Server tương tác với nhau thông qua giao thức POST
GraphQL Schema Definition Language (SDL)
Để Client và Server có chung sự thống nhất và hiểu nhau, chúng ta cần phải định nghĩa Schema để xem Client có thể truy cập được những dữ liệu gì tương tự Server trả về cho client những dữ liệu như thế nào.
Ví dụ Schema của User
1 2 3 4 5 | type Clothes { name: String! style: String! } |
(Dấu ! nghĩa là field của schema sẽ required)
Fetching Data (Query) và Mutation
Client sẽ phải chỉ ra những field nào cần thiết để gửi cho Server
Ví dụ
1 2 3 4 5 6 7 | <span class="token punctuation">{</span> clothes <span class="token punctuation">{</span> name style <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Mutations là cách client truyền dữ liệu lên cho Server để thực hiện thao tác: Create, Update, Delete
Ví dụ:
1 2 3 4 5 6 | mutation { addClothes(name:"adidas_ultraboost", style:"sport") { name } } |
Trên đây là một số khái niệm cần sử dụng trong bài viết này, bây giờ đi vào code các bạn nhé
Dependencies
Ngoài init firebase như các bài viết ở phần trước, lần này chúng ta cần cài thêm một số thư viện nữa
1 2 3 4 | npm install express npm install graphql npm install apollo-server-express |
Thêm các thư viện cần thiết vào file index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">const</span> functions <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"firebase-functions"</span><span class="token punctuation">)</span> <span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> firebaseAdmin <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'firebase-admin'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> productValidate <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./validate/product.store'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> ApolloServer<span class="token punctuation">,</span> gql <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"apollo-server-express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Express */</span> <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// for cloud</span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">initializeApp</span><span class="token punctuation">(</span>functions<span class="token punctuation">.</span><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>firebase<span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token keyword">const</span> api <span class="token operator">=</span> functions<span class="token punctuation">.</span>https<span class="token punctuation">.</span><span class="token function">onRequest</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> api<span class="token punctuation">,</span> <span class="token punctuation">}</span> |
Định nghĩa Schema cho Apollo Server
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">const</span> typeDefs <span class="token operator">=</span> gql<span class="token template-string"><span class="token string">` type Clothes { name: String style: String } type Query { clothes: [Clothes] } type Mutation { addClothes(name: String!, style: String!): Clothes } `</span></span><span class="token punctuation">;</span> |
Mình sẽ định nghĩa một schema là Clothes. Trong Query sẽ có clothes
trả về mảng Clothes, tương tự Mutation sẽ có method addClothes
Định nghĩa resolver cho Apollo Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">const</span> resolvers <span class="token operator">=</span> <span class="token punctuation">{</span> Query<span class="token punctuation">:</span> <span class="token punctuation">{</span> clothes<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">database</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">"clothes"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>snap <span class="token operator">=></span> snap<span class="token punctuation">.</span><span class="token function">val</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>val <span class="token operator">=></span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>key <span class="token operator">=></span> val<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> Mutation<span class="token punctuation">:</span> <span class="token punctuation">{</span> addClothes<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>root<span class="token punctuation">,</span> args<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> clothes <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token punctuation">:</span> args<span class="token punctuation">.</span>name<span class="token punctuation">,</span> style<span class="token punctuation">:</span> args<span class="token punctuation">.</span>style<span class="token punctuation">}</span> <span class="token keyword">await</span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">database</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">"clothes"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>clothes<span class="token punctuation">)</span> <span class="token keyword">return</span> clothes <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> |
Tương tự typeDef, Resolver sẽ cho Apollo Server biết nơi lấy dữ liệu.
- clothes: Để fetch toàn bộ data từ Firebase về, vì GraphQL cần dữ liệu kiểu mảng trong khi Firebase trả về Object vì vậy chúng ta cần biến đổi một chút
- addClothes: Thêm một bản ghi vào Firebase
Sau cùng chúng ta sẽ tạo một 1 instance của ApolloServer, sau đó apply vào express app
1 2 3 | <span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ApolloServer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> typeDefs<span class="token punctuation">,</span> resolvers <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> server<span class="token punctuation">.</span><span class="token function">applyMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">{</span> app<span class="token punctuation">,</span> path<span class="token punctuation">:</span> <span class="token string">"/products"</span><span class="token punctuation">,</span> cors<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
File index.js hoàn chỉnh lúc này:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | <span class="token keyword">const</span> functions <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"firebase-functions"</span><span class="token punctuation">)</span> <span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> firebaseAdmin <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'firebase-admin'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> productValidate <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./validate/product.store'</span><span class="token punctuation">)</span> <span class="token comment">/* Express */</span> <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// for cloud</span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">initializeApp</span><span class="token punctuation">(</span>functions<span class="token punctuation">.</span><span class="token function">config</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>firebase<span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> ApolloServer<span class="token punctuation">,</span> gql <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">"apollo-server-express"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> typeDefs <span class="token operator">=</span> gql<span class="token template-string"><span class="token string">` type Clothes { name: String style: String } type Query { clothes: [Clothes] } type Mutation { addClothes(name: String!, style: String!): Clothes } `</span></span><span class="token punctuation">;</span> <span class="token keyword">const</span> resolvers <span class="token operator">=</span> <span class="token punctuation">{</span> Query<span class="token punctuation">:</span> <span class="token punctuation">{</span> clothes<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">database</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">"clothes"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>snap <span class="token operator">=></span> snap<span class="token punctuation">.</span><span class="token function">val</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>val <span class="token operator">=></span> Object<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>key <span class="token operator">=></span> val<span class="token punctuation">[</span>key<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> Mutation<span class="token punctuation">:</span> <span class="token punctuation">{</span> addClothes<span class="token punctuation">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>root<span class="token punctuation">,</span> args<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> clothes <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token punctuation">:</span> args<span class="token punctuation">.</span>name<span class="token punctuation">,</span> style<span class="token punctuation">:</span> args<span class="token punctuation">.</span>style<span class="token punctuation">}</span> <span class="token keyword">await</span> firebaseAdmin<span class="token punctuation">.</span><span class="token function">database</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ref</span><span class="token punctuation">(</span><span class="token string">"clothes"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>clothes<span class="token punctuation">)</span> <span class="token keyword">return</span> clothes <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> server <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ApolloServer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> typeDefs<span class="token punctuation">,</span> resolvers <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> server<span class="token punctuation">.</span><span class="token function">applyMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">{</span> app<span class="token punctuation">,</span> path<span class="token punctuation">:</span> <span class="token string">"/products"</span><span class="token punctuation">,</span> cors<span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> api <span class="token operator">=</span> functions<span class="token punctuation">.</span>https<span class="token punctuation">.</span><span class="token function">onRequest</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> api<span class="token punctuation">,</span> <span class="token punctuation">}</span> |
Chúng ta bắt đầu deploy bằng cách sử dụng command firebase deploy --only functions
Thử nghiệm
Sau khi deploy thành công chúng ta có thể sử dụng một số tool GraphQL client như GraphiQL, Postman
Mình sẽ sử dụng Postman để test thử các bạn nhé
Như mình đã trình bày ở trên thì Client và phía Server giao tiếp với nhau qua phương thức POST, vì vậy chúng sẽ setting cho Postman tương tự hình dưới đây:
Mutation
Mình sẽ thêm một bản ghi vào DB và get lại tên bằng GraphQL query như hình dưới đây
Query
Lấy toàn bộ thuộc tính name
của tất cả bản ghi
Lấy thêm cả thuộc tính style nữa
Kết
Trên đây là bài tìm hiểu của mình về các dịch vụ của Google Firebase với Nodejs và kết hợp thêm một số thành phần khác mà mình tìm hiểu được. Ngoài những dịch vụ mình sử dụng 3 bài vừa rồi còn rất nhiều dịch vụ khác để tìm hiểu và áp dụng, hẹn gặp các bạn ở các bài viết sau