Trong bối cảnh phát triển web không ngừng thay đổi, GraphQL đã nổi lên như một giải pháp thay thế mạnh mẽ cho các REST API truyền thống, mang đến cho các client khả năng yêu cầu chính xác dữ liệu mà họ cần. Tuy nhiên, sự linh hoạt này có thể tạo ra một loạt thách thức mới, đặc biệt là trong việc duy trì an toàn kiểu dữ liệu (type safety) giữa frontend và backend. Đây là lúc graphql-codegen
xuất hiện, một công cụ mang tính cách mạng giúp tự động tạo mã có kiểu dữ liệu (typed code) từ schema GraphQL của bạn, tăng tốc quy trình phát triển và loại bỏ toàn bộ các lỗi xảy ra trong thời gian chạy (runtime errors).
Bài viết này sẽ đóng vai trò là hướng dẫn giúp bạn hiểu và làm chủ graphql-codegen
. Chúng ta sẽ bắt đầu với các khái niệm cơ bản, đi qua một ví dụ thực tế từng bước về cách thiết lập và cấu hình công cụ, và khám phá các phương pháp hay nhất để tích hợp nó vào dự án của bạn. Cuối hướng dẫn này, bạn sẽ được trang bị kiến thức để tận dụng graphql-codegen
xây dựng các ứng dụng mạnh mẽ hơn, dễ bảo trì hơn và an toàn kiểu dữ liệu hơn.
Bạn muốn một nền tảng tích hợp, Tất cả trong Một cho Nhóm Phát triển của bạn làm việc cùng nhau với năng suất tối đa?
Apidog đáp ứng mọi yêu cầu của bạn và thay thế Postman với mức giá phải chăng hơn nhiều!
graphql-codegen
là gì và Tại sao bạn cần nó?
Về cốt lõi, graphql-codegen
là một công cụ dòng lệnh (command-line tool) kiểm tra schema GraphQL và các thao tác GraphQL (queries, mutations, và subscriptions) của bạn, sau đó tạo ra mã bằng nhiều ngôn ngữ khác nhau. Với mục đích của hướng dẫn dành cho người mới bắt đầu này, chúng ta sẽ tập trung vào trường hợp sử dụng phổ biến nhất của nó: tạo các kiểu TypeScript và hooks cho các ứng dụng frontend.
Vấn đề chính mà graphql-codegen
giải quyết là quy trình tẻ nhạt và dễ gây lỗi khi phải tự viết các interface TypeScript cho cấu trúc dữ liệu của API GraphQL và kết quả của các query. Nếu không có nó, các nhà phát triển sẽ phải làm việc với dữ liệu không có kiểu (mất đi lợi ích của TypeScript) hoặc dành nhiều thời gian tạo và duy trì các kiểu dữ liệu có thể dễ dàng trở nên lỗi thời khi API phát triển.
Lợi ích của việc áp dụng graphql-codegen
rất đa dạng:
- An toàn kiểu dữ liệu từ đầu đến cuối (End-to-End Type Safety): Bằng cách tạo kiểu trực tiếp từ schema của bạn,
graphql-codegen
đảm bảo mã frontend của bạn luôn đồng bộ với mô hình dữ liệu của backend. Điều này có nghĩa là bạn bắt được các lỗi liên quan đến kiểu dữ liệu ngay tại thời điểm biên dịch (compile time), rất lâu trước khi chúng đến tay người dùng. - Cải thiện trải nghiệm nhà phát triển (Improved Developer Experience): Với các kiểu dữ liệu được tạo ra, bạn nhận được các tính năng như tự động hoàn thành (autocompletion) và gợi ý thông minh trong trình soạn thảo mã của mình, giúp phát triển nhanh hơn và hiệu quả hơn. Không còn phải đoán định dạng của các phản hồi API!
- Giảm mã lặp (Reduced Boilerplate):
graphql-codegen
không chỉ tạo kiểu dữ liệu mà còn tạo ra các hook sẵn sàng sử dụng cho các client GraphQL phổ biến như Apollo Client và React Query. Điều này loại bỏ nhu cầu viết logic lấy dữ liệu lặp đi lặp lại. - Tăng cường khả năng bảo trì (Enhanced Maintainability): Khi schema API của bạn thay đổi, chỉ cần một lệnh đơn giản là đủ để tạo lại các kiểu dữ liệu của bạn. Điều này giúp codebase của bạn dễ bảo trì và tái cấu trúc hơn nhiều theo thời gian.
Cách tiếp cận này hoàn toàn phù hợp với triết lý phát triển "schema-first", nơi schema GraphQL đóng vai trò là nguồn thông tin duy nhất cho API của bạn.
Bắt đầu: Thiết lập graphql-codegen
đầu tiên của bạn
Hãy cùng đi sâu vào các khía cạnh thực tế của việc sử dụng graphql-codegen
. Đối với hướng dẫn này, chúng ta sẽ giả định bạn có hiểu biết cơ bản về GraphQL, TypeScript, và đã cài đặt Node.js cùng với một trình quản lý gói (như npm hoặc yarn).
Mục tiêu của chúng ta là thiết lập graphql-codegen
cho một ứng dụng React đơn giản hiển thị danh sách các bài đăng blog.
Bước 1: Khởi tạo dự án và các dependency
Đầu tiên, hãy tạo một dự án React mới với TypeScript và cài đặt các dependency cần thiết.Bash
npx create-react-app my-blog --template typescript
cd my-blog
npm install @apollo/client graphql
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset typescript
Đây là giải thích chi tiết về các dependency chúng ta đã cài đặt:
@apollo/client
: Một client GraphQL phổ biến cho React.graphql
: Một peer dependency được yêu cầu bởi cả Apollo Client vàgraphql-codegen
.@graphql-codegen/cli
: Giao diện dòng lệnh chographql-codegen
.@graphql-codegen/client-preset
: Một preset hiện đại và tinh gọn giúp đơn giản hóa cấu hình cho các ứng dụng phía client.typescript
: Trình biên dịch TypeScript.
Bước 2: Trình hướng dẫn khởi tạo graphql-codegen
Cách dễ nhất để bắt đầu với graphql-codegen
là sử dụng trình hướng dẫn khởi tạo của nó. Chạy lệnh sau trong thư mục gốc của dự án của bạn:Bash
npx graphql-codegen init
Trình hướng dẫn sẽ hỏi bạn một loạt câu hỏi để giúp cấu hình dự án của bạn. Dưới đây là một bộ câu trả lời điển hình cho kịch bản ứng dụng blog của chúng ta:
- Bạn đang xây dựng loại ứng dụng nào?
Application built with React
- Schema của bạn ở đâu? Cung cấp URL đến điểm cuối API GraphQL của bạn. Đối với hướng dẫn này, chúng ta có thể sử dụng một API mẫu có sẵn công khai:
https://swapi-graphql.netlify.app/.netlify/functions/index
- Các thao tác và fragment của bạn ở đâu?
src/**/*.tsx
(Điều này chographql-codegen
biết rằng hãy tìm kiếm các query GraphQL trong tất cả các tệp.tsx
bên trong thư mụcsrc
). - Chọn plugin: Trình hướng dẫn sẽ gợi ý một bộ plugin. Các plugin mặc định, bao gồm
typescript
,typescript-operations
, vàtypescript-react-apollo
, là một điểm khởi đầu tuyệt vời. - Nơi để ghi đầu ra?
src/gql/
(Điều này sẽ tạo một thư mục mớisrc/gql
để lưu trữ các tệp được tạo ra). - Bạn có muốn tạo một tệp introspection không?
Yes
(Điều này có thể hữu ích cho việc phát triển cục bộ và các tiện ích mở rộng của IDE). - Đặt tên tệp cấu hình là gì?
codegen.ts
- Script nào trong package.json nên chạy codegen?
codegen
Sau khi trả lời các câu hỏi này, trình hướng dẫn sẽ tạo một tệp codegen.ts
trong thư mục gốc của dự án và thêm một script codegen
vào tệp package.json
của bạn.
Bước 3: Hiểu về tệp cấu hình (codegen.ts
)
Tệp codegen.ts
là trái tim của thiết lập graphql-codegen
của bạn. Hãy xem một phiên bản đơn giản hóa của những gì trình hướng dẫn tạo ra:TypeScript
import type { CodegenConfig } from '@graphql-codegen/cli';
const config: CodegenConfig = {
overwrite: true,
schema: "https://swapi-graphql.netlify.app/.netlify/functions/index",
documents: "src/**/*.tsx",
generates: {
"src/gql/": {
preset: "client",
plugins: []
}
}
};
export default config;
Hãy phân tích các tùy chọn cấu hình chính:
overwrite: true
: Điều này đảm bảo rằng các tệp được tạo ra hiện có sẽ bị ghi đè mỗi khi bạn chạy lệnh.schema
: Điều này trỏ đến nguồn schema GraphQL của bạn. Nó có thể là một URL đến một điểm cuối trực tiếp, một tệp.graphql
hoặc.json
cục bộ.documents
: Đây là một mẫu glob chographql-codegen
biết nơi tìm các thao tác GraphQL của bạn (queries, mutations, và fragments).generates
: Đây là phần quan trọng nhất của cấu hình. Nó là một đối tượng mà mỗi khóa đại diện cho một tệp hoặc thư mục đầu ra, và giá trị định nghĩa những gì sẽ được tạo ra.preset: "client"
:client-preset
là một cách mạnh mẽ và được khuyến nghị để cấu hìnhgraphql-codegen
cho các ứng dụng frontend. Nó đóng gói nhiều plugin và cung cấp trải nghiệm tinh gọn. Nó tạo ra một hàmgraphql
mà bạn sẽ sử dụng để viết các query của mình.
Bước 4: Viết Query GraphQL đầu tiên của bạn
Bây giờ graphql-codegen
đã được cấu hình, hãy viết một query GraphQL để lấy các bài đăng blog của chúng ta. Tạo một tệp mới src/components/Posts.tsx
:TypeScript
import { gql } from '../gql/gql';
import { useQuery } from '@apollo/client';
const GET_POSTS = gql(`
query GetPosts {
allFilms {
films {
id
title
director
releaseDate
}
}
}
`);
const Posts = () => {
const { loading, error, data } = useQuery(GET_POSTS);
if (loading) return <p>Đang tải...</p>;
if (error) return <p>Lỗi :( </p>;
return (
<div>
{data?.allFilms?.films?.map((film) => (
<div key={film?.id}>
<h2>{film?.title}</h2>
<p>Đạo diễn: {film?.director}</p>
<p>Ngày phát hành: {film?.releaseDate}</p>
</div>
))}
</div>
);
};
export default Posts;
Lưu ý rằng chúng ta đang import một hàm gql
từ tệp src/gql/gql.ts
đã được tạo ra. Đây là hàm gql
được cung cấp bởi client-preset
và chính nó là thứ cho phép phép màu suy luận kiểu dữ liệu (type inference) xảy ra.
Bước 5: Chạy graphql-codegen
và thấy phép màu
Bây giờ, chạy script codegen
từ tệp package.json
của bạn:Bash
npm run codegen
Lệnh này sẽ kiểm tra schema, tìm query GET_POSTS
của bạn và tạo ra các kiểu TypeScript tương ứng trong thư mục src/gql
.
Nếu bây giờ bạn kiểm tra đối tượng data
được trả về bởi hook useQuery
trong Posts.tsx
, bạn sẽ thấy nó được gõ đầy đủ! IDE của bạn sẽ cung cấp tính năng tự động hoàn thành cho data.allFilms.films
, và TypeScript sẽ cảnh báo bạn nếu bạn cố gắng truy cập một trường không tồn tại trong query.
Đi sâu hơn: Một ví dụ frontend blog thực tế
Hãy mở rộng ví dụ của chúng ta để củng cố sự hiểu biết của bạn. Hãy tưởng tượng blog của chúng ta có một schema truyền thống hơn:GraphQL
type Author {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
content: String!
author: Author!
}
type Query {
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
Giả sử schema này đang chạy trên http://localhost:4000/graphql
. Chúng ta sẽ cập nhật tệp codegen.ts
tương ứng:TypeScript
// codegen.ts
// ...
schema: "http://localhost:4000/graphql",
// ...
Bây giờ, hãy tạo một component để hiển thị một bài đăng duy nhất và một component khác để tạo bài đăng mới.
Lấy một bài đăng duy nhấtTypeScript
// src/components/Post.tsx
import { gql } from '../gql/gql';
import { useQuery } from '@apollo/client';
import { useParams } from 'react-router-dom';
const GET_POST = gql(`
query GetPost($id: ID!) {
post(id: $id) {
id
title
content
author {
id
name
}
}
}
`);
const Post = () => {
const { id } = useParams<{ id: string }>();
const { loading, error, data } = useQuery(GET_POST, {
variables: { id },
});
if (loading) return <p>Đang tải...</p>;
if (error) return <p>Lỗi :( </p>;
return (
<div>
<h2>{data?.post?.title}</h2>
<p>Bởi {data?.post?.author?.name}</p>
<p>{data?.post?.content}</p>
</div>
);
};
export default Post;
Sau khi chạy npm run codegen
, các kiểu cho GetPostQuery
và các biến của nó sẽ được tạo ra, cung cấp an toàn kiểu dữ liệu cho hook useQuery
và kết quả của nó.
Tạo một bài đăng mớiTypeScript
// src/components/CreatePost.tsx
import { gql } from '../gql/gql';
import { useMutation } from '@apollo/client';
import { useState } from 'react';
const CREATE_POST = gql(`
mutation CreatePost($title: String!, $content: String!, $authorId: ID!) {
createPost(title: $title, content: $content, authorId: $authorId) {
id
}
}
`);
const CreatePost = () => {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const [createPost, { data, loading, error }] = useMutation(CREATE_POST);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
createPost({ variables: { title, content, authorId: '1' } }); // Giả sử authorId '1' để đơn giản
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Tiêu đề"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<textarea
placeholder="Nội dung"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button type="submit" disabled={loading}>
{loading ? 'Đang tạo...' : 'Tạo bài đăng'}
</button>
{error && <p>Lỗi khi tạo bài đăng: {error.message}</p>}
{data && <p>Bài đăng đã được tạo với ID: {data.createPost.id}</p>}
</form>
);
};
export default CreatePost;
Ở đây, graphql-codegen
tạo ra các kiểu cho CreatePostMutation
và các biến của nó. Điều này đảm bảo rằng khi bạn gọi hàm createPost
, bạn đang truyền đúng kiểu dữ liệu cho title
, content
, và authorId
.
Các khái niệm nâng cao và phương pháp hay nhất
Khi bạn đã quen thuộc hơn với graphql-codegen
, bạn sẽ gặp các tính năng nâng cao và phương pháp hay nhất:
- Fragments:
graphql-codegen
hỗ trợ rất tốt các GraphQL fragment. Bạn có thể định nghĩa fragment trong các tệp.tsx
vàgraphql-codegen
sẽ xử lý đúng các kiểu dữ liệu, thúc đẩy việc tái sử dụng và gom nhóm các dependency dữ liệu cho các component của bạn. - Sử dụng tệp
.graphql
: Để phân tách các mối quan tâm tốt hơn, bạn có thể viết các query, mutation và fragment GraphQL của mình trong các tệp.graphql
chuyên dụng. Chỉ cần cập nhật mảngdocuments
trong tệpcodegen.ts
của bạn để bao gồm phần mở rộng tệp này:documents: ["src/**/*.tsx", "src/**/*.graphql"]
. - Custom Scalars: Nếu schema GraphQL của bạn sử dụng các custom scalar (ví dụ:
Date
,JSON
), bạn có thể ánh xạ chúng sang các kiểu TypeScript tương ứng trong tệpcodegen.ts
của mình để duy trì an toàn kiểu dữ liệu. - Watch Mode: Để có trải nghiệm phát triển liền mạch, bạn có thể chạy
graphql-codegen
ở chế độ watch. Điều này sẽ tự động tạo lại các kiểu dữ liệu của bạn bất cứ khi nào bạn lưu một tệp chứa thao tác GraphQL. Chỉ cần thêm cờ--watch
vào scriptcodegen
trong tệppackage.json
của bạn:"codegen": "graphql-codegen --watch"
.
Khắc phục sự cố thường gặp
Mặc dù graphql-codegen
là một công cụ mạnh mẽ, bạn có thể gặp phải một vài vấn đề phổ biến khi mới bắt đầu:
- "Unable to find schema": Lỗi này thường có nghĩa là đường dẫn
schema
trong tệpcodegen.ts
của bạn không chính xác hoặc máy chủ GraphQL không chạy tại địa chỉ đã chỉ định. - Lỗi cấu hình plugin: Đảm bảo rằng bạn đã cài đặt tất cả các plugin cần thiết và cấu hình của chúng trong tệp
codegen.ts
là chính xác. - Sự cố Peer Dependency
graphql
: Đảm bảo bạn đã cài đặtgraphql
như một dependency trực tiếp trong dự án của mình.
Kết luận: Chào đón tương lai an toàn kiểu dữ liệu
graphql-codegen
không chỉ là một công cụ tạo mã; nó là một sự thay đổi mô hình trong cách chúng ta xây dựng ứng dụng với GraphQL. Bằng cách tận dụng tự động hóa và sức mạnh của schema, bạn có thể tạo ra các codebase mạnh mẽ hơn, dễ bảo trì hơn và thú vị hơn khi làm việc.
Hướng dẫn này đã cung cấp cho bạn nền tảng vững chắc để bắt đầu với graphql-codegen
. Chúng ta đã đi qua các khái niệm cốt lõi, thực hiện một ví dụ thực tế và chạm đến các phương pháp hay nhất. Hành trình không kết thúc ở đây. Hệ sinh thái graphql-codegen
rất rộng lớn, với một bộ sưu tập phong phú các plugin phục vụ cho nhiều framework và trường hợp sử dụng khác nhau. Tôi khuyến khích bạn khám phá tài liệu chính thức, thử nghiệm với các plugin khác nhau và khám phá cách graphql-codegen
có thể cách mạng hóa quy trình phát triển GraphQL của bạn. Chúc bạn code vui vẻ!
Bạn muốn một nền tảng tích hợp, Tất cả trong Một cho Nhóm Phát triển của bạn làm việc cùng nhau với năng suất tối đa?
Apidog đáp ứng mọi yêu cầu của bạn và thay thế Postman với mức giá phải chăng hơn nhiều!