What is GraphQL?

GraphQL là một ngôn ngữ truy vấn mạnh mẽ giúp người dùng chỉ định chính xác dữ liệu mà họ cần trong phản hồi, từ đó giảm thiểu việc trả về dữ liệu thừa hoặc gửi request nhiều lần như trong REST API.

GraphQL là một công nghệ độc lập nền tảng nên nó có thể được triển khai trên nhiều ngôn ngữ lập trình khác nhau, mang lại sự linh hoạt cao cho các hệ thống.

How GraphQL Works

GraphQL sử dụng schema để mô tả cấu trúc dữ liệu của dịch vụ, bao gồm các kiểu dữ liệu (type), các trường (field), và mối quan hệ giữa chúng (relationship). Dữ liệu được mô tả trong schema có thể được tương tác thông qua ba loại thao tác chính:

  • Truy vấn (query): lấy dữ liệu từ server.
  • Biến đổi (mutation): thêm, sửa, hoặc xóa dữ liệu trên server.
  • Đăng ký (subscription): giống như truy vấn nhưng thiết lập một kết nối vĩnh viễn cho phép server chủ động đẩy dữ liệu cập nhật về client theo một định dạng cụ thể.

Tất cả các thao tác này đều được thực hiện thông qua một HTTP endpoint, với phương thức thường dùng là POST.

What is a GraphQL Schema?

Mô tả các kiểu dữ liệu bằng ngôn ngữ schema dễ đọc. Kiểu dữ liệu phổ biến nhất là object và nó thường có nhiều trường. Kiểu dữ liệu của mỗi trường có thể là một object khác, scalar, enum, union, interface hoặc custom type.

Các kiểu dữ liệu trong schema được GraphQL server hiện thực và cung cấp dựa trên request từ client.

Ví dụ về schema mô tả cho một sản phẩm:

#Example schema definition
 
type Product {
	id: ID!
	name: String!
	description: String!
	price: Int
}

Toán tử ! cho biết trường đó là bắt buộc.

Các schema bắt buộc phải có ít nhất một câu truy vấn khả dụng. Thông thường, chúng còn có thêm cả những biến đổi khả dụng.

What Are GraphQL Queries?

Tương tự với GET request của REST API, bao gồm những thành phần sau:

  • Loại thao tác là query: không bắt buộc nhưng được khuyến khích sử dụng vì nó cho server biết chính xác loại request gửi đến là một query.
  • Tên query: có thể là bất cứ thứ gì và cũng không bắt buộc nhưng vẫn nên dùng để phục vụ cho quá trình debug.
  • Cấu trúc dữ liệu: chứa các trường mà server cần trả về.
  • Ngoài ra còn có một hoặc nhiều đối số được dùng để tạo ra các câu truy vấn cho các object cụ thể (xem ví dụ bên dưới). Các đối số của một kiểu dữ liệu mà client có thể sử dụng được định nghĩa ở trong schema.

Ví dụ:

#Example query
 
query myGetProductQuery {
	getProduct(id: 123) {
		name
		description
	}
}

Note

Nếu các đối số của người dùng cung cấp được dùng để truy cập vào các object một cách trực tiếp thì một GraphQL API có thể bị lỗ hổng liên quan đến quản lý quyền truy cập (IDOR).

What Are GraphQL Mutations?

Tương ứng với POST, PUTDELETE request của REST API.

Tương tự với các query, mutation bao gồm loại thao tác, tên và cấu trúc dữ liệu. Tuy nhiên, mutation thường có giá trị đầu vào mà có thể là giá trị inline hoặc một biến.

Ví dụ về một mutation dùng để tạo một product với các giá trị inline (có thể hiểu là giá trị gán cứng):

#Example mutation request
 
mutation {
	createProduct(name: "Flamin' Cocktail Glasses", listed: "yes") {
		id
		name
		listed
	}
}

Response từ server:

#Example mutation response
 
{
	"data": {
		"createProduct": {
			"id": 123,
			"name": "Flamin' Cocktail Glasses",
			"listed": "yes"
		}
	}
}

Components of Queries and Mutations

Variables

Để sử dụng biến ở trong query, ta cần:

  • Khai báo biến và kiểu dữ liệu của nó sau tên query.
  • Thêm tên biến vào những chỗ cần thiết ở trong query.
  • Truyền tên biến và giá trị của nó vào một từ điển chứa các biến (variable dictionary).

Ví dụ:

#Example query with variable
 
query getEmployeeWithVariable($id: ID!) {
	getEmployees(id:$id) {
		name {
			firstname
			lastname
		}
	 }
}
 
Variables:
{
	"id": 1
}

Có thể thấy trong ví dụ trên, tên biến và kiểu dữ liệu được khai báo ở sau tên query. Sau đó, nó được sử dụng làm đối số ở dòng thứ 2 (id:$id).

Aliases

Các object không thể có 2 thuộc tính cùng tên nên câu query sau được xem là không hợp lệ:

#Invalid query
 
query getProductDetails {
	getProduct(id: 1) {
		id
		name
	}
	getProduct(id: 2) {
		id
		name
	}
}

Để giải quyết vấn đề này, ta có thể sử dụng alias:

#Valid query using aliases
 
query getProductDetails {
	product1: getProduct(id: "1") {
		id
		name
	}
	product2: getProduct(id: "2") {
		id
		name
	}
}

Response từ server:

#Response to query
 
{
	"data": {
		"product1": {
			"id": 1,
			"name": "Juice Extractor"
		 },
		"product2": {
			"id": 2,
			"name": "Fruit Overlays"
		}
	}
}

Bằng cách này, ta có thể truy vấn thông tin của nhiều object cùng loại chỉ bằng một request duy nhất thay vì nhiều request như REST API.

Fragments

Là các thành phần mà có thể được tái sử dụng trong các query hoặc mutation. Chúng chứa tập con của các trường thuộc về một kiểu dữ liệu nào đó. Nếu fragment thay đổi thì query hoặc mutation sử dụng nó cũng sẽ bị thay đổi theo.

Ví dụ:

#Example fragment
 
fragment productInfo on Product {
	id
	name
	listed
}
#Query calling the fragment
 
query {
	getProduct(id: 1) {
		...productInfo
		stock
	}
}

Response từ server:

#Response including fragment fields
 
{
	"data": {
		"getProduct": {
			"id": 1,
			"name": "Juice Extractor",
			"listed": "no",
			"stock": 5
		}
	}
}

Subscriptions

Thường được sử dụng trong trường hợp client cần những cập nhật nhỏ về các object theo thời gian thực.

Tương tự với query và mutation, các subscription request cũng sẽ định nghĩa cấu trúc dữ liệu mà server cần trả về.

Subscription thường được hiện thực bằng cách sử dụng WebSocket.

Introspection

Là một tính năng sẵn có của GraphQL cho phép truy vấn thông tin về một schema nào đó. Tính năng này thường được sử dụng bởi các GraphQL IDE và các công cụ tạo documentation.

Tương tự với các query thông thường, chúng ta có thể chỉ định các trường và cấu trúc của response mà ta muốn server trả về. Ví dụ, chúng ta có thể muốn response chỉ chứa tên của các mutation khả dụng.

Introspection có thể tạo ra rủi ro về rò rỉ thông tin vì nó có thể được sử dụng để truy cập những thông tin nhạy cảm chẳng hạn như mô tả của các trường và giúp attacker biết được cách tương tác với API. Tốt nhất là nên tắt tính năng này trong môi trường thực tế.

list
from outgoing([[PortSwigger - GraphQL]])
sort file.ctime asc

Resources