GraphQL — nó là gì, ngôn ngữ truy vấn và ứng dụng trong các dự án di động

Tác giả: IT Sectr Đã đăng: 2026-03-06 Thời gian đọc: 9 phút

GraphQL — là một ngôn ngữ truy vấn cho API và môi trường thực thi để thực hiện các truy vấn đó, được Facebook phát triển vào năm 2012 và mã nguồn mở vào năm 2015. Không giống REST, nơi máy chủ xác định cấu trúc phản hồi, GraphQL cho phép client chỉ định chính xác dữ liệu mình cần, loại bỏ hoàn toàn các vấn đề overfetching và underfetching. Theo State of JavaScript Survey (2025), 35% nhà phát triển được khảo sát sử dụng GraphQL, và trong số các công ty lớn, GitHub, Shopify, Airbnb và The New York Times đã áp dụng nó. GraphQL hỗ trợ ba loại thao tác: query (đọc), mutation (ghi) và subscription (cập nhật thời gian thực qua WebSocket).

Điểm chính

  • GraphQL — ngôn ngữ truy vấn nơi client chỉ định cấu trúc phản hồi
  • Giải quyết các vấn đề overfetching (dữ liệu thừa) và underfetching (dữ liệu không đủ)
  • Hỗ trợ query, mutation và subscription cho các loại thao tác khác nhau
  • Sử dụng một endpoint duy nhất (thường là /graphql) thay vì nhiều URL như trong REST
  • Dựa trên hệ thống kiểu với lược đồ nghiêm ngặt: tất cả dữ liệu có thể được mô tả trước

GraphQL là gì?

GraphQL — là một đặc tả và môi trường thực thi cho API, cung cấp cho client quyền kiểm soát hoàn toàn đối với dữ liệu nhận được. Được phát triển bởi các kỹ sư Facebook để giải quyết các vấn đề của ứng dụng di động News Feed, đặc tả đã được công bố như một tiêu chuẩn mở vào năm 2015. Từ năm 2018, GraphQL được quản lý bởi GraphQL Foundation với sự hỗ trợ của Linux Foundation và các công ty như Apollo, AWS, GitHub, SAP và các công ty khác.

Không giống REST, nơi mỗi endpoint trả về một cấu trúc dữ liệu cố định, GraphQL sử dụng một endpoint duy nhất chấp nhận một chuỗi truy vấn. Client mô tả trong truy vấn các trường mình cần, và máy chủ trả về chính xác những trường đó. Ví dụ, truy vấn { user(id: "1") { name email } } sẽ chỉ trả về tên và email của người dùng, không có các trường bổ sung như address, phone hoặc createdAt mà phải lấy trong REST.

GraphQL không bị ràng buộc với bất kỳ cơ sở dữ liệu hay ngôn ngữ cụ thể nào. Đặc tả chỉ định nghĩa định dạng truy vấn và phản hồi. Có các triển khai máy chủ trên Node.js (graphql-js, Apollo Server), Kotlin (graphql-kotlin, Netflix DGS Framework), Python (Graphene, Strawberry), Ruby (graphql-ruby) và các ngôn ngữ khác. Thư viện client có sẵn cho tất cả các nền tảng chính, bao gồm Apollo Client cho iOS, Android và web.

GraphQL hoạt động như thế nào

Kiến trúc GraphQL bao gồm ba thành phần chính: Lược đồ (Schema), Bộ giải quyết (Resolvers) và Công cụ GraphQL (GraphQL Engine). Lược đồ xác định các kiểu dữ liệu có sẵn, các truy vấn có thể thực hiện và các đối số mà chúng chấp nhận. Bộ giải quyết là các hàm phía máy chủ trả về dữ liệu cho mỗi trường lược đồ. Công cụ nhận truy vấn đầu vào, xác thực nó với lược đồ, gọi các bộ giải quyết thích hợp và tổng hợp phản hồi.

Quy trình xử lý truy vấn như sau:

  • Client gửi yêu cầu POST đến /graphql với phần thân JSON { "query": "..." }
  • Máy chủ phân tích cú pháp truy vấn, xây dựng AST (Cây cú pháp trừu tượng) và xác thực nó với lược đồ
  • Công cụ duyệt qua AST, gọi các bộ giải quyết cho mỗi trường, thu thập dữ liệu
  • Phản hồi được trả về ở định dạng JSON, khớp chặt chẽ với cấu trúc truy vấn

Ưu điểm chính của kiến trúc GraphQL là giải quyết ở cấp trường. Trong REST, nhà phát triển hoặc lấy tất cả các trường của tài nguyên (có thể có dữ liệu thừa) hoặc dựa vào các phần mở rộng như ?fields=name,email. Trong GraphQL, việc lọc này được tích hợp sẵn trong ngôn ngữ: mỗi truy vấn chỉ định rõ ràng các trường cần thiết, và máy chủ trả về chính xác những trường đó. Điều này đặc biệt quan trọng đối với các ứng dụng di động, nơi lượng dữ liệu truyền tải ảnh hưởng trực tiếp đến tốc độ tải và mức tiêu thụ dữ liệu.

Query, Mutation và Subscription

GraphQL định nghĩa ba loại thao tác, mỗi loại tương ứng với một kịch bản tương tác cụ thể. Query — để đọc dữ liệu, tương tự GET trong REST. Mutation — để sửa đổi dữ liệu (tạo, cập nhật, xóa), tương tự POST/PUT/DELETE. Subscription — để cập nhật thời gian thực qua WebSocket, không có tương đồng trực tiếp trong REST cổ điển (cần các giải pháp bổ sung như WebSocket hoặc Server-Sent Events).

Cú pháp truy vấn cơ bản rất trực quan:

js
// Truy vấn đơn giản với đối số
query {
    user(id: "42") {
        name
        email
        avatarUrl
    }
}

// Mutation trả về dữ liệu đã sửa
mutation {
    updateProfile(name: "Ivan") {
        id
        name
        updatedAt
    }
}

// Subscription — lắng nghe cập nhật thời gian thực
subscription {
    newMessage(chatId: "chat_1") {
        id
        text
        sender { name }
    }
}

Query được thực thi song song — tất cả các trường ở cùng cấp được tải đồng thời. Điều này cho phép tải dữ liệu liên quan (người dùng và bài viết của họ) trong một yêu cầu duy nhất mà không cần nhiều vòng lặp. Mutation được thực thi tuần tự — các mutation trong một yêu cầu được thực hiện lần lượt theo thứ tự khai báo. Subscription thiết lập kết nối liên tục qua WebSocket, qua đó máy chủ gửi dữ liệu khi có sự kiện xảy ra.

Các thao tác có thể chấp nhận biến để tách dữ liệu khỏi truy vấn, chỉ thị (@include, @skip) để bao gồm trường có điều kiện và đoạn để tái sử dụng các tập hợp trường. Những khả năng này làm cho các truy vấn GraphQL trở nên linh hoạt và có thể tái sử dụng, đặc biệt quan trọng trong các dự án lớn với nhiều màn hình và thành phần.

Lược đồ và hệ thống kiểu của GraphQL

Trọng tâm của GraphQL là hệ thống kiểu mô tả tất cả dữ liệu và thao tác API có thể có. Lược đồ là mô tả về các kiểu mà máy chủ có thể trả về và các truy vấn mà nó chấp nhận. Lược đồ được viết bằng Ngôn ngữ Định nghĩa Lược đồ (SDL) và hoạt động như một hợp đồng giữa client và máy chủ. Client có thể lấy lược đồ thông qua nội quan (introspection) — một truy vấn đặc biệt __schema trả về mô tả đầy đủ về API.

Ví dụ lược đồ cho blog:

js
// SDL — Schema Definition Language
type User {
    id: ID!
    name: String!
    email: String
    posts: [Post!]!
}

type Post {
    id: ID!
    title: String!
    content: String
    author: User!
}

type Query {
    user(id: ID!): User
    posts(page: Int): [Post!]!
}

Dấu chấm than (!) biểu thị trường không thể null — nó được đảm bảo sẽ có mặt trong phản hồi. Dấu ngoặc vuông [ ] biểu thị một danh sách. GraphQL hỗ trợ các kiểu vô hướng (Int, Float, String, Boolean, ID), kiểu đối tượng, enum, union, interface và kiểu đầu vào (cho đối số mutation). Việc phân loại nghiêm ngặt tự động tạo tài liệu cho API và cho phép các công cụ client tạo mã: kiểu TypeScript, lớp dữ liệu Kotlin, cấu trúc Swift.

Nội quan (Introspection) là tính năng độc đáo của GraphQL không có trong REST. Client có thể gửi một truy vấn đến lược đồ và nhận được mô tả đầy đủ về tất cả các kiểu, trường, đối số và chỉ thị. Đây là nền tảng của các công cụ như GraphiQL và Apollo Studio, tự động tạo tài liệu và tự động hoàn thành cho các nhà phát triển. Nội quan cũng cho phép viết các bài kiểm tra tự động xác minh sự tuân thủ của lược đồ với cấu trúc dự kiến.

So sánh GraphQL với REST

Lựa chọn giữa GraphQL và REST là một trong những quyết định kiến trúc chính khi thiết kế API. Cả hai phương pháp đều có ưu và nhược điểm, và lựa chọn phụ thuộc vào yêu cầu cụ thể của dự án. REST vượt trội về tính đơn giản và phổ quát, GraphQL vượt trội về tính linh hoạt và hiệu quả truy vấn. Hãy xem bảng so sánh.

Tiêu chíRESTGraphQL
Cấu trúc phản hồiCố định, do máy chủ xác địnhLinh hoạt, do client xác định
OverfetchingThường xuyên — máy chủ trả về tất cả trườngKhông — client chỉ yêu cầu trường cần thiết
Số lượng yêu cầuNhiều vòng lặpMột yêu cầu cho tất cả dữ liệu
Bộ nhớ đệmBộ nhớ đệm HTTP tự nhiênCần cấu hình thủ công
Phân loạiKhông tích hợp sẵn (phụ thuộc định dạng)Nghiêm ngặt, thông qua lược đồ SDL
Công cụcurl, Postman, SwaggerGraphiQL, Apollo Studio, Nội quan
Tải tệp lênTự nhiên qua multipartCần giao thức bổ sung
Hiệu suấtCó thể dự đoán, dễ tối ưu hơnPhụ thuộc vào độ phức tạp truy vấn lồng ghép

Nhược điểm chính của GraphQL là độ phức tạp của bộ nhớ đệm. Trong REST, bộ nhớ đệm HTTP hoạt động ở cấp URL: một yêu cầu đến /api/users/42 luôn trả về cùng một cấu trúc, và phản hồi có thể được lưu vào bộ nhớ đệm theo URL. Trong GraphQL, tất cả các yêu cầu đều đến một endpoint duy nhất và cấu trúc phản hồi phụ thuộc vào phần thân yêu cầu. Để giải quyết vấn đề này, Apollo Client sử dụng bộ nhớ đệm được chuẩn hóa ở phía client, chia các phản hồi thành các thực thể riêng lẻ theo id và tự động cập nhật chúng khi nhận được dữ liệu mới.

Một khía cạnh quan trọng khác là vấn đề N+1. Khi yêu cầu dữ liệu lồng ghép (ví dụ, bài viết của người dùng và nhận xét cho mỗi bài viết), GraphQL có thể thực hiện một truy vấn SQL riêng cho mỗi mục trong danh sách. Vấn đề này được giải quyết bằng DataLoader — một tiện ích để gộp và lưu vào bộ nhớ đệm các truy vấn cơ sở dữ liệu, nhóm các yêu cầu riêng lẻ thành một lô duy nhất. Trong REST, vấn đề này ít rõ ràng hơn vì nhà phát triển kiểm soát cấu trúc phản hồi ở phía máy chủ.

Ví dụ truy vấn GraphQL

Hãy xem các ví dụ thực tế về việc sử dụng GraphQL trong ứng dụng di động Kotlin với Apollo Client. Các ví dụ thể hiện các kịch bản điển hình: tải dữ liệu cho màn hình hồ sơ (query), tạo bài viết mới (mutation) và đăng ký nhận xét mới (subscription). Mỗi ví dụ bao gồm cả truy vấn GraphQL và mã phía client.

Query: Tải hồ sơ kèm bài viết

Một truy vấn GraphQL duy nhất tải người dùng, bài viết mới nhất và tổng số người theo dõi. Trong REST, việc này cần ít nhất 2-3 yêu cầu: /users/42, /users/42/posts, /users/42/stats. GraphQL kết hợp chúng thành một vòng lặp duy nhất, giảm thời gian tải màn hình trên các kết nối chậm.

kotlin
// Truy vấn GraphQL (trong tệp .graphql)
query ProfileScreen($userId: ID!) {
    user(id: $userId) {
        name
        bio
        avatarUrl
        posts(limit: 10) {
            id
            title
            createdAt
        }
        followersCount
        followingCount
    }
}

// Lời gọi phía client (Apollo Client + Kotlin)
val response = apolloClient
    .query(ProfileScreenQuery(userId = "42"))
    .execute()
binding.nameText.text = response.data?.user?.name

Mutation: Tạo bài viết mới

Mutation không chỉ tạo tài nguyên mà còn trả về dữ liệu hiện tại của nó để cập nhật giao diện người dùng. Trường __typename được Apollo Client sử dụng để chuẩn hóa bộ nhớ đệm — client sẽ tự động cập nhật bản ghi Post trong bộ nhớ đệm khi nhận được phản hồi mutation thành công.

kotlin
// Mutation GraphQL
mutation CreatePost($input: CreatePostInput!) {
    createPost(input: $input) {
        id
        title
        createdAt
        author {
            id
            name
        }
    }
}

// Lời gọi mutation với kiểu input
val input = CreatePostInput(
    title = "Bài viết mới về GraphQL",
    content = "GraphQL đơn giản hóa việc làm việc với API..."
)
val result = apolloClient
    .mutation(CreatePostMutation(input))
    .execute()

Một lợi thế quan trọng của GraphQL so với REST trong bối cảnh phát triển di động là tự động tạo mã. Apollo Client cho Kotlin (Apollo GraphQL) tạo các lớp an toàn về kiểu từ các tệp .graphql tại thời điểm xây dựng. Nếu máy chủ thay đổi lược đồ, dự án sẽ không được xây dựng cho đến khi các truy vấn được cập nhật. Điều này ngăn ngừa các lỗi thời gian chạy điển hình trong REST, nơi các thay đổi cấu trúc phản hồi có thể không bị phát hiện trong quá trình phát triển.

Hệ sinh thái: Apollo, Relay và công cụ

Hệ sinh thái GraphQL bao gồm một số thư viện và công cụ chính giúp đơn giản hóa việc phát triển và vận hành. Apollo Client là thư viện client phổ biến nhất, hỗ trợ React, iOS, Android và Kotlin Multiplatform. Relay của Facebook là một giải pháp thay thế cho các ứng dụng React với cách tiếp cận độc đáo đối với quản lý dữ liệu và bộ nhớ đệm. Lựa chọn giữa Apollo và Relay phụ thuộc vào nền tảng và yêu cầu về hiệu suất.

Ộ phía máy chủ, các nhà lãnh đạo là Apollo Server (Node.js), Netflix DGS Framework (Kotlin/Java) và graphql-ruby. Để phát triển lược đồ và kiểm tra truy vấn, GraphiQL được sử dụng — một IDE tương tác được tích hợp trong trình duyệt. Apollo Studio cung cấp các chỉ số hiệu suất, theo dõi truy vấn và quản lý lược đồ cho môi trường sản xuất. Riêng biệt, đáng chú ý là GraphQL Code Generator — một công cụ tạo các kiểu TypeScript, Kotlin, Swift và Dart từ lược đồ SDL.

Đối với phát triển di động, Apollo Kotlin (Apollo GraphQL) được quan tâm đặc biệt — một thư viện được viết hoàn toàn bằng Kotlin với hỗ trợ coroutine, Flow và Multiplatform. Nó cho phép sử dụng các truy vấn GraphQL thống nhất cho Android và iOS trong các dự án Kotlin Multiplatform. Apollo Kotlin chuẩn hóa bộ nhớ đệm, hỗ trợ lỗi ở cấp trường (lỗi một phần) và tự động tạo mô hình dữ liệu từ các tệp .graphql. Điều này làm cho GraphQL trở thành lựa chọn ưu tiên cho các dự án di động lớn, nơi tốc độ phát triển và an toàn kiểu là quan trọng.

Câu hỏi thường gặp

GraphQL có thay thế REST không?

GraphQL không thay thế REST, mà đưa ra một cách tiếp cận thay thế. REST phù hợp hơn cho các API CRUD đơn giản, bộ nhớ đệm HTTP và các API công cộng với tải có thể dự đoán. GraphQL tối ưu cho các giao diện phức tạp với nhiều dữ liệu liên quan.

Việc di chuyển từ REST sang GraphQL có khó không?

Việc di chuyển có thể thực hiện dần dần: GraphQL có thể hoạt động như một lớp (cổng) phía trước các dịch vụ REST hiện có. Nhiều công ty thêm GraphQL bên cạnh REST mà không tắt API cũ. Việc thay thế hoàn toàn đòi hỏi viết lại các bộ giải quyết.

Vấn đề N+1 trong GraphQL là gì?

N+1 xảy ra khi một truy vấn SQL riêng được thực hiện cho mỗi mục trong danh sách. Nó được giải quyết bằng DataLoader — một thư viện gộp các yêu cầu riêng lẻ thành một và lưu vào bộ nhớ đệm kết quả trong một yêu cầu HTTP duy nhất.

GraphQL xử lý tải tệp lên như thế nào?

Đặc tả GraphQL không định nghĩa trực tiếp việc tải tệp lên. Trong thực tế, các phương pháp sau được sử dụng: mã hóa base64 (đơn giản nhưng không hiệu quả cho tệp lớn), yêu cầu multipart theo giao thức graphql-multipart-request-spec hoặc một endpoint REST riêng cho tệp.

GraphQL có an toàn không?

Bảo mật GraphQL yêu cầu các biện pháp bổ sung: giới hạn độ sâu lồng ghép, giới hạn độ phức tạp truy vấn, giới hạn tốc độ ở cấp thao tác. Nội quan lược đồ công khai có thể tiết lộ cấu trúc dữ liệu — nên tắt nó trong môi trường sản xuất.

Tổng kết

  • GraphQL — ngôn ngữ truy vấn nơi client điều khiển cấu trúc phản hồi, loại bỏ overfetching và underfetching
  • Ba loại thao tác: query (đọc), mutation (ghi), subscription (thời gian thực)
  • Sử dụng một endpoint duy nhất và hệ thống kiểu nghiêm ngặt — lược đồ SDL
  • Không giống REST, giải quyết vấn đề nhiều vòng lặp — tất cả dữ liệu trong một yêu cầu
  • Cần DataLoader để ngăn ngừa vấn đề N+1 và cấu hình bộ nhớ đệm thủ công
  • Client chính: Apollo Client (Android, iOS, Web) và Relay (React)
  • Phù hợp nhất cho các giao diện phức tạp với nhiều thực thể liên quan và ứng dụng di động

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm