Sử dụng server action trong nextjs 14 để call API – Phần 2

Vinh Phan

server action trong nextjs

Ở phần 1, mình đã giới thiệu cho mọi người cách gọi server action trong nextjs 14 rồi, bây giờ chúng ta cùng tiếp tục đi vào những phần sâu hơn nhé!

Các bạn hãy xem lại phần 1 để hiểu rõ bài viết

Hiển thị trạng thái tải khi thực hiện hành động

Chúng ta có thể sử dụng hook useFormStatus để hiển thị trạng thái tải trong khi biểu mẫu đang được gửi. Là một hook, nó chỉ có thể tồn tại trong một thành phần máy khách và chỉ có thể được sử dụng như một phần tử con của một thành phần biểu mẫu bằng cách sử dụng server action.

Bây giờ, hãy cập nhật thành phần hiện tại của chúng tôi, ArticleComment và thay thế nút này bằng một thành phần tùy chỉnh do chúng tôi tạo ra có tên là AddCommentButton.

import { addComment } from '@/services/actions/comment'
import { AddCommentButton } from '@/components/AddCommentButton'
 
export default async function ArticleComment(props) {
  return (
    <form action={addComment}>
      <input type="text" name="articleId" value={props.articleId} />
      <input type="text" name="comment" />
            <AddCommentButton />
    </form>
  )
}

Và tất nhiên, bạn sẽ phải tạo một thành phần như vậy:

'use client'
 
import { useFormStatus } from 'react-dom'
 
export function AddCommentButton() {
  const { pending } = useFormStatus();
 
  return (
    <button type="submit" aria-disabled={pending}>
      Add
    </button>
  )
}

Bây giờ, khi biểu mẫu được gửi, trạng thái mới sẽ được phản ánh trong biến đang chờ xử lý và sẽ điều chỉnh thành phần nút tương ứng. Bạn có thể mở rộng mã này để cung cấp nhiều phản hồi trực quan hơn cho người dùng và tạo trải nghiệm người dùng tốt hơn.

Gửi tệp đến server action

Nếu bạn cần gửi nhiều hơn dữ liệu có thể tuần tự hóa JSON, như văn bản và số, hoặc cụ thể hơn, nếu bạn cần gửi và xử lý việc tải tệp lên, đừng lo lắng. Server action sẽ thực thi

Bạn có thể gửi tệp tới server action giống như cách bạn đã làm với server action cho đến nay, bằng cách sử dụng tính năng gửi biểu mẫu với

<input type="file /> hoặc các thư viện như dropzone.

Về phía máy chủ, mọi thứ có thể khác tùy theo nhu cầu của bạn. Ví dụ: bạn có thể lưu tệp, chuyển đổi tệp, v.v. Quá trình truy xuất tệp sẽ giống nhau.

Hãy xem xét một server action mẫu xử lý việc tải tệp lên và truyền tệp tới API:

// /services/actions/upload-file
'use server'

export async function uploadFile(formData) {
  const comment = formData.get('file');
  const arrayBuffer = await file.arrayBuffer();
  const buffer = new Uint8Array(arrayBuffer);
  await new Promise((resolve, reject) => {
    upload_stream({}, function (error, result) {
      if (error) {
        reject(error);
        return;
      }
      resolve(result);
    })
    .end(buffer);
  });
}

Một số chức năng hay sử dụng tác vụ server action

Tôi cũng chia sẻ một số mẹo về cách truy cập các tài nguyên được bảo vệ bằng server action Auth0 và Next.js.

  • Tách rời các server action. Như chúng tôi đã đề cập ở trên, có thể ghi server action trong một tệp riêng biệt, nhưng trong trường hợp bạn không muốn sử dụng lại chức năng của mình, bạn có thể viết tương tự trong server component. Tuy nhiên, như chúng ta đã học được từ kinh nghiệm hoặc cuốn sách “Clean Code”, việc tách biệt các mối quan tâm là rất quan trọng để giữ cho mã dễ hiểu, dễ bảo trì, tái sử dụng và kiểm tra hơn. Vì vậy, hãy cố gắng tách biệt giữa các thành phần và hành động.
  • Đừng xem nhẹ phía client. Khi ranh giới giữa server và client bị sự tác động của server action, bạn có thể dễ dàng quên hoặc coi thường cách xử lý giao diện người dùng đúng cách. Ví dụ: việc chuyển từ xác thực sang máy chủ có thể giảm một vài dòng mã nhưng lại ảnh hưởng đến trải nghiệm người dùng.
  • Lưu trữ kết quả của server action. Nếu server action trả về dữ liệu không thường xuyên thay đổi, bạn có thể lưu kết quả vào bộ nhớ đệm để chúng không phải tìm nạp chúng từ máy chủ mỗi lần. Việc tránh tìm nạp dữ liệu không cần thiết có thể cải thiện hiệu suất ứng dụng của bạn.
  • Sử dụng Tác vụ máy chủ để xử lý lỗi một cách khéo léo. Nếu server action không thành công, bạn nên xử lý lỗi một cách khéo léo và trả về thông báo lỗi có ý nghĩa cho người dùng.
  • Bảo vệ server action của bạn. Khi nói đến bảo mật, đừng nghĩ về server action của bạn khác với những gì bạn nghĩ nếu cùng một mã tồn tại trong điểm cuối API. Server action cần được bảo mật và bảo vệ khỏi sự truy cập trái phép.

gọi protected api endpoint

Các API được bảo vệ không thể truy cập công khai và yêu cầu một số hình thức xác thực hoặc ủy quyền trước khi có thể truy cập chúng. Điều này rất quan trọng để bảo vệ dữ liệu nhạy cảm và ngăn chặn truy cập trái phép.

Có nhiều cơ chế bạn có thể triển khai để bảo vệ API, ví dụ:

  • API Key: Khóa API là cách đơn giản và hiệu quả để bảo vệ API. Chúng là những chuỗi bí mật được trao cho khách hàng được ủy quyền. Khi khách hàng gửi yêu cầu tới API, nó phải bao gồm khóa API của mình trong yêu cầu. API sau đó sẽ kiểm tra khóa API để đảm bảo rằng nó hợp lệ trước khi xử lý yêu cầu.
  • Access token: là một cách khác để bảo vệ API. Chúng là các mã thông báo tồn tại trong thời gian ngắn được tạo bởi máy chủ xác thực và được gửi đến ứng dụng để phản hồi xác thực người dùng thành công. Sau đó, ứng dụng sẽ sử dụng mã thông báo truy cập để truy cập API bằng cách gửi mã thông báo đó theo yêu cầu của nó. Mã thông báo truy cập có thể được triển khai dưới nhiều hình thức khác nhau, với tùy chọn phổ biến là Mã thông báo web JSON (JWT).

API KEY để bảo vệ API endpoints

API key là một chuỗi ký tự bí mật được sử dụng để xác định và xác thực ứng dụng khi thực hiện yêu cầu đối với API. Khóa API thường được sử dụng để bảo vệ API khỏi bị truy cập trái phép và theo dõi việc sử dụng.

Mặc dù khóa API có thể xác định một dự án hoặc ứng dụng cụ thể đang thực hiện lệnh gọi API nhưng chúng không xác định được từng người dùng thực hiện yêu cầu. Đây là một hạn chế bảo mật đáng kể vì nó cho phép bất kỳ ai có khóa API đều có thể truy cập API, bất kể họ là ai. Tìm hiểu thêm về OAuth 2.0

Việc triển khai khóa API khác nhau tùy theo từng dự án, do đó, tùy thuộc vào cách API mục tiêu của bạn triển khai khóa API, cách bạn sử dụng server action để thực hiện lệnh gọi API có thể khác nhau.

Hãy xây dựng lại server action addComment của chúng tôi để chuyển khóa API cho API mục tiêu.

// /services/actions/comment
'use server'

export async function addComment(formData) {
    const articleId = formData.get('articleId');
    const comment = formData.get('comment');
  const response = await fetch(`https://api.example.com/articles/${articleId}/comments`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    'x-api-key': process.env.CMS_API_KEY // 👈 New Code
    },
    body: JSON.stringify({ comment }),
  });
  const result = await response.json();
  return result;
}

Vì server action chạy trong máy chủ nên việc đọc khóa API từ các biến môi trường là an toàn, tuy nhiên, bạn nên đảm bảo nhà cung cấp môi trường của mình an toàn để làm điều đó. Ví dụ: nếu bạn triển khai bằng Vercel, tất cả các giá trị biến môi trường sẽ tự động được mã hóa khi lưu trữ.

ACcess token để bảo vệ API endpoints

Access token được sử dụng trong xác thực dựa trên mã thông báo, cho phép các ứng dụng truy cập API. Ứng dụng nhận được mã thông báo truy cập sau quá trình ủy quyền và xác thực người dùng thành công. Mã thông báo này sau đó được chuyển dưới dạng thông tin xác thực khi thực hiện lệnh gọi API. Mã thông báo được trình bày sẽ thông báo cho API rằng người mang đã được ủy quyền truy cập API và thực hiện các hành động cụ thể được xác định theo phạm vi được cấp trong quá trình ủy quyền.

Vì vậy, chúng ta có thể chia quy trình thành quy trình gồm hai bước:

  • Xác thực: Xác định người dùng và truy xuất access token
  • Gọi endpoint: Sử dụng access token đã nhận và có thể xác minh để gọi điểm cuối API

Sau khi người dùng của bạn được xác thực, bạn có thể viết lại server action của mình như sau:

// /services/actions/comment
'use server'
import { getAccessToken } from '@auth0/nextjs-auth0' // 👈 New Code

export async function addComment(formData) {
  const accessToken = await getAccessToken(); // 👈 New Code
  const articleId = formData.get('articleId');
  const comment = formData.get('comment');
  const response = await fetch(`https://api.example.com/articles/${articleId}/comments`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${accessToken.accessToken}`, // 👈 New Code
    },
    body: JSON.stringify({ comment }),
  });
  const result = await response.json();
  return result;
}

Kết luận

Server action là một tính năng mới tuyệt vời trong Next.js cho phép các nhà phát triển làm được nhiều việc hơn với ít mã hơn bằng cách loại bỏ phần lớn bản soạn sẵn cần thiết để viết mã API và mã gọi tiếp theo của chúng.

Hi vọng qua bài viết này, bạn có thể triển khai tính năng server action trong dự án nextjs của mình

Viết một bình luận