Khóa học
GraphQL là một cách mạnh mẽ và hiệu quả để xây dựng API. Client truy vấn API tương tự như cách họ truy vấn một cơ sở dữ liệu, và API đó chỉ trả về đúng dữ liệu họ yêu cầu, thường giúp giảm kích thước phản hồi và cải thiện thời gian phản hồi. Thời gian phản hồi thấp là yếu tố then chốt trong thế giới hiện đại.
Khi API GraphQL kết hợp với MongoDB, bạn không chỉ có thời gian phản hồi nhanh mà còn có một định dạng dữ liệu nhất quán từ đầu đến cuối.
Hãy tưởng tượng: Client của bạn đang thực thi một truy vấn GraphQL trông khá giống JSON. Khi dữ liệu đến ứng dụng của bạn — giả sử là TypeScript trong ví dụ này — bạn tiếp tục làm việc với một định dạng dữ liệu tương tự JSON ngay trong ứng dụng. Tiến thêm một bước nữa, khi làm việc với MongoDB, dữ liệu bạn gửi đến và nhận từ MongoDB cũng sẽ tương tự JSON. Vậy là bạn có được trải nghiệm dữ liệu nhất quán đi kèm hiệu năng. Không cần quá bận tâm về việc thao tác và định dạng dữ liệu, thay vào đó bạn có thể tập trung vào trải nghiệm người dùng của ứng dụng, thay vì cơ sở dữ liệu và công cụ.
Trong hướng dẫn này, chúng ta sẽ xem việc dùng MongoDB trong API GraphQL dễ dàng thế nào, lần này được xây dựng với TypeScript.
Điều kiện tiên quyết
Đối với hướng dẫn cụ thể này, trước khi bắt đầu, chúng ta cần chuẩn bị sẵn một vài thứ:
- Một cụm MongoDB Atlas
- Node.js 22+
- MongoDB Node.js Driver 6.x
Trong hướng dẫn này, chúng ta sẽ không làm gì quá phức tạp về mặt cơ sở dữ liệu, nên bất kỳ cụm MongoDB ở tầng nào cũng hoạt động, kể cả tầng miễn phí. Tuy nhiên, chúng ta sẽ không đi sâu vào việc cung cấp (provision) một cụm MongoDB Atlas trong bài này. Kỳ vọng là bạn đã có sẵn quy tắc người dùng và quy tắc mạng. Nếu cần trợ giúp để bắt đầu, hãy xem tài liệu Get Started With Atlas về chủ đề này.
Bạn cần tạo sẵn một ứng dụng Node.js. Nếu muốn, bạn có thể chạy các lệnh sau:
mkdir graphql_example
cd graphql_example
npm init -y
Các lệnh trên tạo một thư mục dự án mới và một tệp package.json cơ bản. Chúng ta sẽ dùng TypeScript, Express Framework, MongoDB và Apollo trong dự án này. Mỗi thành phần có thể được cài đặt bằng các lệnh sau:
npm install @apollo/server@4 body-parser cors dotenv express@4 graphql mongodb
npm install @types/cors @types/express@4 @types/node tsx typescript --save-dev
Các lệnh trên sẽ cài đặt các phụ thuộc cũng như các định nghĩa kiểu dùng cho TypeScript.
Ở phần cuối của điều kiện tiên quyết, bạn cần có tệp cấu hình cho TypeScript. Ở thư mục gốc của dự án, tạo tệp tsconfig.json với JSON sau:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Bất kỳ lúc nào bạn muốn xem toàn bộ dự án hoặc tự thử nghiệm, bạn có thể xem trên GitHub.
Tạo nền tảng với các phụ thuộc Apollo, Express và MongoDB
Về cấu trúc dự án, chúng ta sẽ sử dụng các đường dẫn sau:
- src/database/connection.ts
- src/database/userService.ts
- src/resolvers/index.ts
- src/schema/typeDefs.ts
- src/types/index.ts
- src/index.ts
- .env
Tệp src/index.ts sẽ chứa toàn bộ phần khởi tạo Express Framework và Apollo. Nghĩa là khởi động server, định nghĩa endpoint GraphQL, v.v. Tệp src/types/index.ts sẽ chứa các định nghĩa TypeScript để làm việc với mô hình dữ liệu cơ sở dữ liệu trong ứng dụng. Đừng nhầm lẫn với tệp src/schema/typeDefs.ts vốn tương tự nhưng tập trung vào định nghĩa kiểu của GraphQL.
Điều này để lại các thư mục database và resolvers. Phần lớn logic ứng dụng và cơ sở dữ liệu của chúng ta sẽ nằm trong các tệp này — chúng ta sẽ khám phá sớm.
Hiện tại, hãy tập trung cấu hình server trong tệp src/index.ts:
import 'dotenv/config';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import http from 'http';
import cors from 'cors';
import bodyParser from 'body-parser';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './resolvers';
async function startServer() {
try {
const app = express();
const httpServer = http.createServer(app);
const server = new ApolloServer({
typeDefs,
resolvers,
});
await server.start();
app.use(
'/graphql',
cors<cors.CorsRequest>(),
bodyParser.json(),
expressMiddleware(server)
);
const PORT = process.env.PORT || 3000;
await new Promise<void>((resolve) => httpServer.listen({ port: PORT }, resolve));
console.log(Server ready at http://localhost:${PORT}/graphql);
} catch (error) {
console.error('Error starting server:', error);
process.exit(1);
}
}
startServer();
Chúng ta sẽ mở rộng tệp trên khi tiến triển.
Trước hết, chúng ta import toàn bộ phụ thuộc của dự án. Tiếp theo, cấu hình server Express Framework và middleware Apollo.
const server = new ApolloServer({
typeDefs,
resolvers,
});
Chúng ta vẫn chưa thêm gì vào tệp src/schema/typeDefs.ts và src/resolvers/index.ts, nhưng ở giai đoạn này, chúng ta đang chuẩn bị mọi thứ. Server sẽ chưa chạy thành công cho đến khi chúng ta hoàn thiện các phần còn thiếu của những tệp khác.
Cũng đáng lưu ý là ví dụ này cho phép yêu cầu từ mọi origin vì dòng sau:
cors<cors.CorsRequest>(),
Điều này phù hợp cho môi trường phát triển, nhưng trong triển khai production, hãy chỉ định những origin bạn cần.
Tương tác với MongoDB từ ứng dụng TypeScript với Node.js
Mặc dù nền tảng Express Framework và Apollo server đã sẵn sàng, chúng ta vẫn chưa cấu hình MongoDB trong ứng dụng. Trước khi vào phần mã, chúng ta cần thêm thông tin kết nối vào tệp .env:
MONGODB_URI="mongodb+srv://<USERNAME>:<PASSWORD>@<HOST>/?appName=devrel-graphql"
DB_NAME="graphql_example"
Các giá trị <USERNAME>, <PASSWORD> và <HOST> nên được thay bằng thông tin cụm MongoDB Atlas của bạn trong bảng điều khiển MongoDB Atlas.
Tiếp theo, hãy thiết lập logic kết nối cơ sở dữ liệu cho ứng dụng. Trong tệp **src/database/connection.ts**, thêm đoạn mã sau:
import { MongoClient, Db } from 'mongodb';
let db: Db | null = null;
let client: MongoClient | null = null;
export async function connectToDatabase(): Promise<Db> {
if (db) {
return db;
}
try {
const MONGODB_URI = process.env.MONGODB_URI;
if (!MONGODB_URI) {
throw new Error('MONGODB_URI is not set');
}
const DB_NAME = process.env.DB_NAME || 'graphql_example';
const APP_NAME = process.env.APP_NAME || 'devrel-graphql';
console.log('Connecting to MongoDB...');
client = new MongoClient(MONGODB_URI, { appName: APP_NAME });
await client.connect();
db = client.db(DB_NAME);
console.log(Successfully connected to MongoDB database: ${DB_NAME});
return db;
} catch (error) {
console.error('MongoDB connection error:', error);
throw error;
}
}
export function getDatabase(): Db {
if (!db) {
throw new Error('Database not initialized. Call connectToDatabase() first.');
}
return db;
}
export async function closeDatabase(): Promise<void> {
if (client) {
await client.close();
db = null;
client = null;
console.log('MongoDB connection closed');
}
}
Ý tưởng ở đây là tạo một kết nối singleton. Có nhiều cách khác để tích hợp MongoDB vào ứng dụng, nhưng đây là cách chúng ta chọn cho ứng dụng này.
Hàm connectToDatabase sẽ lấy thông tin kết nối từ tệp **.env**, kết nối và trả về cơ sở dữ liệu bạn đã định nghĩa. Nếu thất bại, lỗi sẽ được bắt và ném ra. Bạn chỉ cần dùng connectToDatabase khi thiết lập server như ở tệp trước đó.
Hàm getDatabase sẽ được dùng mỗi khi có yêu cầu đến ứng dụng. Nó cho phép lấy phiên bản kết nối hiện tại.
Cuối cùng, để giữ mọi thứ gọn gàng, chúng ta có hàm closeDatabase để đóng kết nối cơ sở dữ liệu khi server dừng. Tùy bạn có muốn dùng hàm này không, nhưng có nó là một thói quen tốt.
Trước khi sang phần logic cơ sở dữ liệu, có lẽ nên thiết lập các kiểu TypeScript cho các đối tượng truyền dữ liệu (DTO):
export interface User {
id: string;
name: string;
email: string;
age?: number;
createdAt: string;
}
export interface CreateUserInput {
name: string;
email: string;
age?: number;
}
export interface UpdateUserInput {
id: string;
name?: string;
email?: string;
age?: number;
}
Các interface trên, nằm trong tệp **src/types/index.ts**, được API của chúng ta quản lý, nhưng logic cơ sở dữ liệu vẫn cần nhận biết chúng. Chỉ một lát nữa bạn sẽ thấy.
Tiếp theo là phần tương tác cơ sở dữ liệu thực tế cho tạo, truy xuất, cập nhật, xóa (CRUD), được thực hiện trong tệp src/database/userService.ts. Ý tưởng là resolver GraphQL sẽ xử lý logic ứng dụng và gọi các hàm trong tệp service này, còn service sẽ xử lý logic cơ sở dữ liệu.
Trong tệp src/database/userService.ts, chúng ta có đoạn mã sau:
import { Collection, ObjectId } from 'mongodb';
import { getDatabase } from './connection';
import { User, CreateUserInput, UpdateUserInput } from '../types';
interface UserDocument {
_id: ObjectId;
name: string;
email: string;
age?: number;
createdAt: Date;
}
export class UserService {
private collection: Collection<UserDocument>;
constructor() {
const db = getDatabase();
this.collection = db.collection<UserDocument>('users');
}
private toUser(doc: UserDocument): User {
return {
id: doc._id.toString(),
name: doc.name,
email: doc.email,
age: doc.age,
createdAt: doc.createdAt.toISOString(),
};
}
async getAllUsers(): Promise<User[]> {
const users = await this.collection.find().toArray();
return users.map(doc => this.toUser(doc));
}
async getUserById(id: string): Promise<User | null> {
try {
const doc = await this.collection.findOne({ _id: new ObjectId(id) });
return doc ? this.toUser(doc) : null;
} catch (error) {
return null;
}
}
async createUser(input: CreateUserInput): Promise<User> {
const newUser: Omit<UserDocument, '_id'> = {
name: input.name,
email: input.email,
age: input.age,
createdAt: new Date(),
};
const result = await this.collection.insertOne(newUser as UserDocument);
const createdUser = await this.collection.findOne({ _id: result.insertedId });
if (!createdUser) {
throw new Error('Failed to create user');
}
return this.toUser(createdUser);
}
async updateUser(input: UpdateUserInput): Promise<User | null> {
try {
const updateFields: Partial<Omit<UserDocument, '_id'>> = {};
if (input.name !== undefined) updateFields.name = input.name;
if (input.email !== undefined) updateFields.email = input.email;
if (input.age !== undefined) updateFields.age = input.age;
const result = await this.collection.findOneAndUpdate(
{ _id: new ObjectId(input.id) },
{ $set: updateFields },
{ returnDocument: 'after' }
);
return result ? this.toUser(result) : null;
} catch (error) {
return null;
}
}
async deleteUser(id: string): Promise<boolean> {
try {
const result = await this.collection.deleteOne({ _id: new ObjectId(id) });
return result.deletedCount > 0;
} catch (error) {
return false;
}
}
async createIndexes(): Promise<void> {
await this.collection.createIndex({ email: 1 }, { unique: true });
await this.collection.createIndex({ createdAt: -1 });
console.log('Database indexes created');
}
}
Điều đầu tiên bạn sẽ chú ý là interface sau:
interface UserDocument {
_id: ObjectId;
name: string;
email: string;
age?: number;
createdAt: Date;
}
Nó trông giống interface User, nhưng không hoàn toàn giống. UserDocument được mô hình hóa sát hơn với những gì cơ sở dữ liệu làm việc. Trong cơ sở dữ liệu, chúng ta dùng ObjectId, còn trong ứng dụng là chuỗi. Trong MongoDB, id được biểu diễn là _id thay vì id như trong interface User.
Điều này dẫn đến lớp UserService.
Sau khi lấy handle tới collection từ instance cơ sở dữ liệu thông qua phương thức constructor, chúng ta có loạt hàm sau:
- toUser
- getAllUsers
- getUserById
- createUser
- updateUser
- deleteUser
- createIndexes
Hàm toUser về bản chất là một hàm trợ giúp chuyển đổi giữa User và UserDocument. Chúng ta gửi và nhận UserDocument tới và từ cơ sở dữ liệu, và cần chuyển đổi để có thể gửi và nhận User trong ứng dụng.
async createIndexes(): Promise<void> {
await this.collection.createIndex({ email: 1 }, { unique: true });
await this.collection.createIndex({ createdAt: -1 });
console.log('Database indexes created');
}
Hàm createIndexes sẽ tạo một vài index ví dụ có thể hữu ích. Mặc định, chỉ có index trên trường _id, điều này ổn cho một bản demo với rất ít tài liệu, nhưng khi ứng dụng phát triển, đừng bỏ qua việc tạo index phù hợp dựa trên dữ liệu và nhu cầu truy vấn. Hai index trên chỉ là ví dụ về những gì bạn có thể cần.
Giờ chúng ta có thể xem từng hàm service, bắt đầu với hàm getAllUsers:
async getAllUsers(): Promise<User[]> {
const users = await this.collection.find().toArray();
return users.map(doc => this.toUser(doc));
}
Hàm trên sẽ tìm tất cả tài liệu trong collection vì không có tiêu chí lọc trong hàm find. Kết quả được chuyển đổi bằng hàm toUser và trả về cho ứng dụng, sau này sẽ là các resolver Apollo của chúng ta.
async getUserById(id: string): Promise<User | null> {
try {
const doc = await this.collection.findOne({ _id: new ObjectId(id) });
return doc ? this.toUser(doc) : null;
} catch (error) {
return null;
}
}
Hàm getByUserId tương tự, nhưng lần này chúng ta dùng findOne và chỉ định tiêu chí lọc — ở đây là muốn lấy một người dùng dựa trên trường _id duy nhất. Vì trường id trong interface User là chuỗi, chúng ta cần bọc nó trong đối tượng ObjectId để phù hợp với truy vấn trong MongoDB.
Tiếp theo là tạo dữ liệu trong MongoDB thông qua hàm createUser:
async createUser(input: CreateUserInput): Promise<User> {
const newUser: Omit<UserDocument, '_id'> = {
name: input.name,
email: input.email,
age: input.age,
createdAt: new Date(),
};
const result = await this.collection.insertOne(newUser as UserDocument);
const createdUser = await this.collection.findOne({ _id: result.insertedId });
if (!createdUser) {
throw new Error('Failed to create user');
}
return this.toUser(createdUser);
}
Sau khi dùng hàm insertOne để chèn dữ liệu, chúng ta dùng insertedId trả về để truy vấn lại dữ liệu đó. Làm vậy vì trong API GraphQL, khi tạo dữ liệu, thường kỳ vọng là có thể truy vấn dữ liệu đó ngay trong cùng mutation.
Hàm updateUser thì hơi khác:
async updateUser(input: UpdateUserInput): Promise<User | null> {
try {
const updateFields: Partial<Omit<UserDocument, '_id'>> = {};
if (input.name !== undefined) updateFields.name = input.name;
if (input.email !== undefined) updateFields.email = input.email;
if (input.age !== undefined) updateFields.age = input.age;
const result = await this.collection.findOneAndUpdate(
{ _id: new ObjectId(input.id) },
{ $set: updateFields },
{ returnDocument: 'after' }
);
return result ? this.toUser(result) : null;
} catch (error) {
return null;
}
}
Trong ứng dụng này, chúng ta chỉ cho phép cập nhật các trường name, email và age. Tất cả các trường này là tùy chọn, nên chúng ta phải thiết lập trong tiêu chí cập nhật nếu có mặt.
Điều này dẫn đến hàm findOneAndUpdate.
Đối tượng đầu tiên của hàm là tiêu chí lọc. Chúng ta chỉ quan tâm cập nhật khi _id khớp. Với mọi kết quả khớp, dùng toán tử $set để thay thế hoặc tạo các trường có trong đối tượng updateFields. Các trường không có trong đối tượng sẽ không thay đổi. Cuối cùng, vì mục tiêu là vừa tìm vừa cập nhật chứ không chỉ cập nhật, ta đặt returnDocument thành after để nhận tài liệu đã thay đổi làm phản hồi, thay vì tài liệu ban đầu. Nếu dùng updateOne thay vì findOneAndUpdate, chúng ta chỉ nhận được thông tin về thao tác cập nhật, chứ không có dữ liệu.
Thao tác CRUD cuối cùng cho API này là xóa dữ liệu khỏi MongoDB, được thực hiện qua hàm deleteUser:
async deleteUser(id: string): Promise<boolean> {
try {
const result = await this.collection.deleteOne({ _id: new ObjectId(id) });
return result.deletedCount > 0;
} catch (error) {
return false;
}
}
Hàm deleteOne ở trên trông giống findOne vì chúng ta cũng dùng tiêu chí lọc dựa trên _id. Khác ở chỗ mọi bản ghi khớp — tối đa một tài liệu trong trường hợp này — sẽ bị xóa.
Đến đây, logic cơ sở dữ liệu đã sẵn sàng. Để hoàn tất, chúng ta cần kết nối từ tệp **src/index.ts**:
// Previous imports here...
import { connectToDatabase, closeDatabase } from './database/connection';
import { UserService } from './database/userService';
async function startServer() {
try {
await connectToDatabase();
const userService = new UserService();
await userService.createIndexes();
// The rest of the file here...
}
Trong đoạn mã trên, phần lớn tệp src/index.ts đã được lược bớt. Mục tiêu chỉ là minh họa việc dùng hàm connectToDatabase và tạo index. Rõ ràng là bạn chỉ cần chèn ba dòng đó gần đầu hàm startServer.
Bây giờ, chúng ta có thể tập trung vào logic API.
Định nghĩa kiểu GraphQL, resolver và logic ứng dụng tùy biến
Chúng ta đã có các kiểu TypeScript để làm việc với cơ sở dữ liệu cũng như truyền dữ liệu trong ứng dụng. Tuy nhiên, chúng ta vẫn chưa tạo định nghĩa kiểu GraphQL — thứ xác định người dùng được phép tương tác gì khi sử dụng API của chúng ta.
Trong tệp src/schema/typeDefs.ts, thêm nội dung sau:
export const typeDefs = #graphql
type User {
id: ID!
name: String!
email: String!
age: Int
createdAt: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!, age: Int): User!
updateUser(id: ID!, name: String, email: String, age: Int): User
deleteUser(id: ID!): Boolean!
}
;
Kiểu User là những gì client được phép yêu cầu trong mỗi truy vấn. Trong ví dụ này, nó khớp với interface User, nhưng không nhất thiết phải vậy. Ví dụ, có thể interface User của bạn chứa dữ liệu không bao giờ nên trả về cho client, như mật khẩu.
Kiểu Query định nghĩa các loại truy vấn có thể thực thi trong API. Mỗi truy vấn sẽ trả về một User hoặc một mảng User, nhưng chúng ta có thể cài đặt logic cụ thể trong các truy vấn này ở giữa.
Kiểu Mutation tương tự Query, nhưng dùng cho các truy vấn thao tác dữ liệu.
Khi đã có định nghĩa kiểu GraphQL, chúng ta có thể tập trung vào logic resolver cho ứng dụng. Trong tệp src/resolvers/index.ts của dự án, thêm đoạn mã sau:
import { UserService } from '../database/userService';
import { User, CreateUserInput, UpdateUserInput } from '../types';
export const resolvers = {
Query: {
users: async (): Promise<User[]> => {
const userService = new UserService();
return await userService.getAllUsers();
},
user: async (_: unknown, { id }: { id: string }): Promise<User | null> => {
const userService = new UserService();
return await userService.getUserById(id);
},
},
Mutation: {
createUser: async (
_: unknown,
{ name, email, age }: CreateUserInput
): Promise<User> => {
const userService = new UserService();
return await userService.createUser({ name, email, age });
},
updateUser: async (
_: unknown,
{ id, name, email, age }: UpdateUserInput
): Promise<User | null> => {
const userService = new UserService();
return await userService.updateUser({ id, name, email, age });
},
deleteUser: async (_: unknown, { id }: { id: string }): Promise<boolean> => {
const userService = new UserService();
return await userService.deleteUser(id);
},
},
};
Các resolver trên chỉ là phần mở rộng cho những gì chúng ta thấy trong tệp định nghĩa kiểu cho query và mutation.
Hãy xem truy vấn users:
users: async (): Promise<User[]> => {
const userService = new UserService();
return await userService.getAllUsers();
},
Truy vấn GraphQL này sẽ sử dụng UserService — một phần của logic cơ sở dữ liệu — để lấy toàn bộ người dùng. Phản hồi User[] sẽ được gửi lại cho người dùng, nhưng chỉ những phần họ yêu cầu và khớp với định nghĩa kiểu GraphQL.
Ví dụ, giả sử client gửi truy vấn sau:
query {
users {
name
email
}
}
Trong ví dụ trên, chúng ta có nhiều trường để làm việc, nhưng client chỉ muốn các trường name và email. Việc này được xử lý dựa trên các định nghĩa kiểu GraphQL mà client đang dùng. Apollo xử lý phần “ma thuật” để điều đó xảy ra.
Quy tắc tương tự áp dụng cho mọi query và mutation trong tệp **src/resolvers/index.ts** của chúng ta.
createUser: async (
_: unknown,
{ name, email, age }: CreateUserInput
): Promise<User> => {
const userService = new UserService();
return await userService.createUser({ name, email, age });
},
Với định nghĩa kiểu createUser, chúng ta thấy client có thể truyền các trường name, email và age. Trong hàm resolver createUser, chúng ta làm tương tự và gọi hàm createUser từ service cơ sở dữ liệu. Phản hồi sẽ trả về cho client dựa trên các trường họ yêu cầu.
Build và chạy API GraphQL
Đến thời điểm này, API GraphQL đã ở trạng thái rất tốt. Có một vài cách để build và chạy ứng dụng này.
Ví dụ, bạn có thể thêm đoạn sau vào tệp **package.json**:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts",
"dev:watch": "tsx watch src/index.ts",
"watch": "tsc -w"
},
Nếu bạn chọn cách này, có thể chạy npm run build để build dự án và npm run start để bắt đầu phục vụ dự án. Cổng mặc định là 3000, có thể kiểm thử bằng cURL hoặc bất kỳ công cụ ưa thích nào.
Kết luận
Bạn vừa thấy cách tạo một API GraphQL với TypeScript và MongoDB làm cơ sở dữ liệu. Dù ví dụ mang tính đơn giản, chúng ta đã xây dựng các truy vấn và mutation để thực hiện thao tác CRUD với cơ sở dữ liệu. MongoDB là sự kết hợp hoàn hảo khi dùng trong API GraphQL vì dữ liệu bạn làm việc có định dạng tương tự từ đầu đến cuối. Ví dụ, client chạy một truy vấn trông giống JSON. Dữ liệu truyền qua truy vấn có thể dùng với rất ít định dạng và điều chỉnh, và tương tự khi gửi đến và từ cơ sở dữ liệu. Trải nghiệm liền mạch giúp bạn tập trung vào trải nghiệm người dùng và bớt phải thao tác dữ liệu để đáp ứng cơ sở dữ liệu.
Nếu bạn gặp khó khăn ở bất kỳ bước nào trong hướng dẫn này, hãy xem dự án hoàn chỉnh trên GitHub.
FAQs
Tại sao tôi nên dùng GraphQL?
GraphQL tuyệt vời vì bạn có thể truy vấn một API như cách bạn truy vấn cơ sở dữ liệu, chỉ yêu cầu đúng những gì bạn cần. Điều này giúp phía client nhẹ và hiệu quả.
Apollo có phải là lựa chọn duy nhất cho GraphQL không?
Apollo giúp việc tạo API GraphQL trở nên rất dễ dàng, nhưng không phải lựa chọn duy nhất. Tuy nhiên, các lựa chọn khác nằm ngoài phạm vi của hướng dẫn này.
Vì sao dùng MongoDB với GraphQL?
Cả hai đều làm việc với định dạng dữ liệu giống JSON, vì vậy bạn không cần liên tục chuyển đổi giữa các cấu trúc dữ liệu khác nhau khi thông tin đi từ cơ sở dữ liệu đến API rồi tới client.
Resolver làm gì?
Resolver là một hàm cho GraphQL biết cách truy xuất hoặc sửa đổi dữ liệu cho một trường cụ thể trong schema của bạn.
Sự khác nhau giữa query và mutation là gì?
Queries dùng để đọc/lấy dữ liệu (giống các yêu cầu GET), còn mutations dùng để tạo, cập nhật hoặc xóa dữ liệu (giống POST, PUT, DELETE).

Nic Raboy là Trưởng bộ phận Quan hệ Nhà phát triển tại MongoDB, nơi anh dẫn dắt một đội ngũ các nhà phát triển Python, Java, C# và PHP tạo ra nội dung xuất sắc giúp các nhà phát triển triển khai MongoDB thành công trong dự án của mình. Anh có kinh nghiệm với Golang và JavaScript, và thường xuyên viết về nhiều hành trình phát triển của bản thân.