Xây dựng GraphQL API với Node.js và Apollo Server: Từ Schema đến Mutation

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Vì sao REST API không còn là “duy nhất”?

Nhớ lại hồi mình mới chập chững làm Web, REST API lúc đó đúng là “tiêu chuẩn vàng”. Cứ mỗi lần cần dữ liệu mới, mình lại hì hục tạo thêm endpoint: /users, /posts, /comments. Mọi thứ vẫn ổn cho đến khi dự án phình to. Anh em Frontend bắt đầu than phiền: “Này, mình chỉ cần cái tên User thôi, sao API trả về cả email, địa chỉ, ngày sinh làm gì cho nặng máy?” hay “Để hiển thị một bài viết, mình phải gọi tận 3-4 API khác nhau mới gom đủ data”.

Lúc đó mình mới thấm cái cảnh Over-fetching (lấy thừa) và Under-fetching (lấy thiếu). GraphQL ra đời để dẹp bỏ những rắc rối này. Thay vì Backend áp đặt cấu trúc dữ liệu, GraphQL trao quyền cho Client: bạn cần gì, server trả đúng cái đó.

Sự khác biệt thực tế: Cơm phần vs Buffet

REST API: Ăn theo thực đơn cố định

REST giống như bạn vào quán cơm bình dân. Bạn gọi “Suất cơm gà”, chủ quán bưng ra đủ bộ: cơm, gà, canh và dưa chua. Dù bạn ghét dưa chua, nó vẫn lù lù trên đĩa (Over-fetching). Muốn ăn thêm miếng sườn? Bạn phải trả tiền và đợi gọi thêm một đĩa nữa (Under-fetching).

GraphQL: Tiệc Buffet dữ liệu

GraphQL thì khác hẳn, nó giống một bữa tiệc buffet. Bạn cầm chiếc đĩa (Query) và chỉ gắp đúng những món mình thèm. Muốn lấy nameavatar? Xong ngay. Muốn lấy luôn title của các bài viết mà user đó đã đăng trong cùng một lần lấy? Cứ bỏ vào đĩa là xong.

Được và mất khi áp dụng vào dự án

Trong một dự án Dashboard mình từng tham gia, việc chuyển sang GraphQL đã giúp giảm số lượng request từ 8 cái xuống còn đúng 1 cái mỗi lần load trang. Tuy nhiên, đừng thần thánh hóa nó quá.

Tại sao nên dùng?

  • Tiết kiệm băng thông: Payload giảm đáng kể, cực kỳ hữu ích cho người dùng 4G/5G có tốc độ không ổn định.
  • Hợp đồng chặt chẽ: Schema đóng vai trò như bản cam kết giữa Frontend và Backend. Sai kiểu dữ liệu là hệ thống “la làng” ngay lập tức.
  • Gom về một mối: Bạn không cần nhớ hàng chục URL phức tạp, mọi thứ chỉ xoay quanh endpoint duy nhất là /graphql.

Những rào cản cần lưu ý:

  • Caching đau đầu: Vì dùng phương thức POST cho mọi request, việc tận dụng HTTP Caching tự nhiên của trình duyệt khó hơn REST rất nhiều.
  • Tư duy mới: Team bạn cần thời gian để làm quen với cách nghĩ theo dạng đồ thị (Graph) thay vì các bảng phẳng truyền thống.

Công cụ chiến lược: Apollo Server

Có khá nhiều thư viện để xây dựng GraphQL, nhưng Apollo Server luôn là lựa chọn hàng đầu của mình. Nó không chỉ ổn định mà còn đi kèm Apollo Sandbox cực đỉnh. Công cụ này giúp bạn test API trực quan, nhìn rõ cấu trúc dữ liệu thay vì phải căng mắt đọc những dòng JSON thô kệch.

Bắt tay vào code thôi!

Trước khi bắt đầu, hãy chắc chắn máy bạn đã cài Node.js. Chúng ta sẽ làm một ví dụ thực tế: quản lý Sách và Tác giả.

Bước 1: Thiết lập nền móng

mkdir node-graphql-demo
cd node-graphql-demo
npm init -y
npm install apollo-server graphql

Bước 2: Vẽ chân dung dữ liệu (Schema)

Tạo file index.js. Đây là nơi chúng ta định nghĩa cấu trúc dữ liệu bằng ngôn ngữ SDL (Schema Definition Language):

const { ApolloServer, gql } = require('apollo-server');

const typeDefs = gql`
  type Book {
    id: ID!
    title: String
    author: Author
  }

  type Author {
    id: ID!
    name: String
    books: [Book]
  }

  type Query {
    books: [Book]
    book(id: ID!): Book
  }

  type Mutation {
    addBook(title: String!, authorId: ID!): Book
  }
`;

Bước 3: Hiện thực hóa logic (Resolvers)

Nếu Schema là cái khung thì Resolver chính là bộ não. Nó quyết định dữ liệu sẽ được lấy từ đâu (Database, file JSON hay API bên thứ ba).

const authors = [
  { id: '1', name: 'Nam Cao' },
  { id: '2', name: 'Tô Hoài' },
];

const books = [
  { id: '1', title: 'Lão Hạc', authorId: '1' },
  { id: '2', title: 'Dế Mèn Phiêu Lưu Ký', authorId: '2' },
];

const resolvers = {
  Query: {
    books: () => books,
    book: (_, args) => books.find(b => b.id === args.id),
  },
  Book: {
    author: (parent) => authors.find(a => a.id === parent.authorId),
  },
  Mutation: {
    addBook: (_, { title, authorId }) => {
      const newBook = { id: String(books.length + 1), title, authorId };
      books.push(newBook);
      return newBook;
    }
  }
};

Bước 4: Kích hoạt Server

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server đã sẵn sàng tại: ${url}`);
});

Kiểm chứng kết quả

Chạy lệnh node index.js và mở trình duyệt theo URL được cấp. Hãy thử chạy đoạn Query này:

query {
  books {
    title
    author {
      name
    }
  }
}

Bạn sẽ thấy kết quả trả về đúng như mong đợi: tên sách kèm tên tác giả chỉ trong một nốt nhạc. Điểm hay ở chỗ: nếu bạn bỏ trường author, server sẽ tự động bỏ qua logic tìm kiếm tác giả, giúp tiết kiệm tài nguyên xử lý đáng kể.

Bài học xương máu khi làm dự án thực tế

Sai lầm lớn nhất của mình trước đây là nhét tất cả Schema vào một file schema.graphql duy nhất. Khi dự án chạm mốc 50+ thực thể, file này trở thành một mớ hỗn độn. Hãy chia nhỏ chúng ra theo module ngay từ đầu (User, Product, Order…).

Thêm một lưu ý nhỏ: GraphQL luôn trả về mã lỗi 200. Đừng chỉ check mã HTTP như khi làm REST. Hãy tập thói quen kiểm tra mảng errors trong cục data trả về để bắt lỗi chính xác hơn.

Hy vọng bài viết này giúp anh em bớt “ngại” GraphQL. Chỉ cần nắm vững tư duy về Schema, anh em sẽ thấy việc xây dựng API trở nên thú vị và linh hoạt hơn rất nhiều.

Share: