Blitz.js là một framework React ban đầu được fork từ Next.js. Hôm nay chúng ta sẽ xây dựng ứng dụng To-Do Blitz.js lưu trữ task trong Upstash. Không nói thêm, hãy bắt đầu!
Thiết lập
Bạn sẽ cần cài Blitz.js trên máy tính để bắt đầu.
NPM:
npm install -g blitz --legacy-peer-depsYarn:
yarn global add blitzĐể tạo app Blitz.js mới, dùng blitz new và cd vào thư mục.
blitz new blitzjs-todo && cd blitzjs-todoTuyệt, bây giờ cài TailwindCSS để style website.
blitz install tailwindCuối cùng, cài Upstash JS SDK để gọi API Upstash dễ dàng hơn.
NPM:
npm i @upstash/redisYarn:
yarn i @upstash/redisLúc này, bạn nên chạy blitz dev để chắc chắn mọi thứ hoạt động đúng. Thử tạo tài khoản và đăng nhập. Nó nên trông như thế này nếu bạn làm đúng cho đến nay.

Ngoài ra, cấu trúc file của bạn nên trông như thế này:

Sao chép UPSTASH_REDIS_REST_URL và UPSTASH_REDIS_REST_TOKEN từ Upstash Console vào file .env cho bây giờ. Nó nên trông như thế này:
# This env file should be checked into source control
# This is the place for default values for all environments
# Values in `.env.local` and `.env.production` will override these values
UPSTASH_REDIS_REST_URL=YOUR_URL_HERE
UPSTASH_REDIS_REST_TOKEN=YOUR_TOKEN_HEREChúng ta đã thiết lập ứng dụng Blitz.js hoàn toàn! Hãy bắt đầu triển khai danh sách To-Do.
Triển khai
Blitz.js đi kèm với Xác thực Người dùng tích hợp sẵn! Hãy tận dụng này để tạo danh sách to-do riêng tư cho mỗi người dùng.
Trước hết, hãy khởi tạo Upstash JS SDK trong /lib/redis.ts
// lib/redis.ts
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
export default redis;Chúng ta sẽ cần tạo 3 API Route khác nhau để truy cập danh sách To-Do.
Điều hướng đến app/api và tạo file tên getall.ts. Khi đã làm, dán mã sau vào:
// app/api/getall.ts
import { BlitzApiRequest, BlitzApiResponse, getSession } from "blitz";
import redis from "../../lib/redis";
export const handler = async (req: BlitzApiRequest, res: BlitzApiResponse) => {
const session = await getSession(req, res);
if (!session.userId) {
res.status(401).json({
error: `Do not tamper with this route!`
});
} else {
await redis
.lrange(String(session.userId), 0, 100)
.then((data) => res.status(200).json({
data: data,
success: true
}))
.catch((error) => res.status(500).json({
error: error
}));
}
};
export default handler;Hãy xem cách API Route này hoạt động, từng bước. Trước hết, chúng ta tạo request đến route. Trên route, chúng ta xác thực người dùng đã đăng nhập. Nếu không có user, trả về phản hồi "Not Authorized". Nếu có user, thì chúng ta truy xuất cơ sở dữ liệu Upstash Redis để tìm tất cả To-Do hiện có trong danh sách. Sẽ lấy khoảng 100 To-Do.
Q: Đợi, chúng ta thêm To-Do như thế nào? A: Câu hỏi hay! Hãy làm ngay bây giờ!
Một lần nữa, dán mã sau vào file mới tên add.ts trong app/api.
// app/api/add.ts
import { BlitzApiRequest, BlitzApiResponse, getSession } from "blitz";
import redis from "../../lib/redis";
const handler = async (req: BlitzApiRequest, res: BlitzApiResponse) => {
const session = await getSession(req, res);
if (req.method !== "POST" || !req.body.data || !session.userId) {
res.status(401).json({
error: `Do not tamper with this route!`
});
} else {
let todo = encodeURI(req.body.data);
await redis
.lpush(String(session.userId), todo)
.then(() => res.status(200).json({
success: true
}))
.catch(() => res.status(500).json({
error: "Error adding data."
}));
}
};
export default handler;API route này khá giống cái trước, nhưng chú ý chúng ta thêm nhiều kiểm tra hơn ở dòng 5. Đó là vì request này không phải GET, mà là POST. Chú ý cách chúng ta kiểm tra ba điều. Trước, chắc chắn request thực sự là POST. Tiếp, chắc chắn có JSON hoặc text trong req.body.data. Cuối, chắc chắn user đã đăng nhập. Nếu tất cả các kiểm tra nhỏ này qua, chúng ta có thể đẩy To-Do vào danh sách Redis trên Upstash. Nếu có bất kỳ lỗi nào khi lấy, chúng ta có thể trả về 500 bằng .catch.
Route cuối cùng chúng ta cần thêm là xóa To-Do. Khi bạn hoàn thành cái gì, bạn phải gạch chéo nó ra khỏi danh sách! Hãy thêm API Route cuối trong app/api/remove.ts. Sao chép mã sau vào file:
// app/api/remove.ts
import { BlitzApiRequest, BlitzApiResponse, getSession } from "blitz";
import redis from "../../lib/redis";
const handler = async (req: BlitzApiRequest, res: BlitzApiResponse) => {
const session = await getSession(req, res);
if (req.method !== "POST" || !req.body.data || !session.userId) {
res.status(401).json({
error: `Do not tamper with this route!`
});
} else {
let todo = encodeURI(req.body.data);
await redis
.lrem(String(session.userId), 1, todo)
.then(() => res.status(200).json({
success: true
}))
.catch(() => res.status(500).json({
error: "Error removing data."
}));
}
};
export default handler;Nhận thấy gì giống nhau? Đó là vì route này gần như giống hệt add API Route. Khác biệt lớn ở đây là chúng ta dùng LREM, không phải LPUSH, để xóa item khỏi Redis.
Xây dựng Frontend
Để bắt đầu, hãy xóa mọi thứ trong app/pages/index.js và viết danh sách To-Do từng bước.
Ở đầu file, dán các import này.
import { Link, BlitzPage, useMutation, Routes, getAntiCSRFToken } from "blitz";
import { useRef, useEffect, useState, Suspense } from "react";
import Layout from "app/core/layouts/Layout";
import { useCurrentUser } from "app/core/hooks/useCurrentUser";
import logout from "app/auth/mutations/logout";Chúng ta sẽ dùng React Hooks để xây dựng chức năng cốt lõi của danh sách To-Do. Hãy triển khai một số tính năng cốt lõi.
const Main = () => {
const todoRef = useRef(null)
const [todos, setTodos] = useState([])
const currentUser = useCurrentUser()
const [logoutMutation] = useMutation(logout)
const handleAddTodo = async (e) => {
e.preventDefault()
const antiCSRFToken = await getAntiCSRFToken()
const response = await fetch("/api/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
"anti-csrf": antiCSRFToken,
},
body: JSON.stringify({
data: todoRef.current?.value
}),
})
const data = await response.json()
if (data.success) {
todoRef.current!.value = ""
fetchTodos()
}
}
const handleRemoveTodo = async (id) => {
const antiCSRFToken = await getAntiCSRFToken()
const response = await fetch("/api/remove", {
method: "POST",
headers: {
"Content-Type": "application/json",
"anti-csrf": antiCSRFToken,
},
body: JSON.stringify({
data: id
}),
})
const data = await response.json()
if (data.success) {
fetchTodos()
}
}
const fetchTodos = async () => {
const antiCSRFToken = await getAntiCSRFToken()
const response = await fetch("/api/getall", {
method: "GET",
headers: {
"anti-csrf": antiCSRFToken,
},
})
const res = await response.json()
setTodos(res.data)
}
useEffect(() => {
fetchTodos()
}, [])
if (currentUser) {
return (
<>
User id: {currentUser.id}
User email: {currentUser.email}
{(todos as string[]).map((todo: string, index: number) => (
{todo}
))}
>
)
} else {
return (
)
}
} Component <Main/> là cốt lõi của ứng dụng. Nhìn kỹ mã của nó cho thấy cách chúng ta dùng nó. Ở đầu component, chúng ta khởi tạo state cho ứng dụng. Chúng ta cũng khai báo ref để dùng sau trong input "New To-Do". Bạn cũng có thể nhận thấy việc dùng antiCSRFToken! Blitz.js yêu cầu dùng token này khi fetch bất kỳ API Route nào để ngăn chặn bất kỳ tác nhân độc hại nào hại trang web. Thật tốt có nó!
Chúng ta dùng ba hàm chính để xử lý dữ liệu trên website. Ba hàm này là:
handleAddTodohandleRemoveTodofetchTodos
Chúng ta gọi fetchTodos ngay khi trang tải, để load tất cả To-Do user còn cần hoàn thành. Khi user xóa hoặc thêm To-Do, chúng ta gọi fetchTodos lại để phản ánh thay đổi trên website!
Nếu user chưa đăng nhập, user sẽ được nhắc đăng nhập trước khi thấy trang này.
Bạn có thể đăng ký hoặc đăng nhập nếu chưa có session trên trang web. Nhớ rằng, bạn không thể lưu To-Do mà không có tài khoản, và tất cả API Route yêu cầu bạn xác thực với AntiCSRFToken!
Nhưng đợi, còn một bước quan trọng! Chúng ta phải export trang!
const Home: BlitzPage = () => {
return (
);
};
Home.suppressFirstRenderFlicker = true;
Home.getLayout = (page) => {page} ;
export default Home;Như bạn thấy, Blitz.js dùng cách tiếp cận hơi khác Next, nhưng cốt lõi giống nhau cho đến nay. Chúng ta dùng Suspense đã import trước để cho user thấy app đang load, rồi hiển thị component </Main> sau khi load xong!
Để thấy thay đổi có hiệu lực, chạy lệnh này trong console nữa, và điều hướng đến app trong trình duyệt.
blitz devNếu bạn làm theo hướng dẫn, ứng dụng của bạn nên trông như thế này sau khi đăng nhập và thêm vài To-Do:

Bạn có thể xóa To-Do bằng cách nhấp vào ô bên cạnh nó, đó là việc của hàm removeTodo 😉.
Chúc mừng!
Tôi hy vọng bạn học được điều gì mới đọc bài viết này, và nếu không, nhớ rằng ôn tập kỹ năng không bao giờ hại! Blitz.js đang chuyển trọng tâm khỏi Next.js, nên có thể là framework hoàn toàn khác trong tương lai, nhưng hãy theo dõi trang web của họ tại đây!
Nguồn dự án: GitHub Link
Demo hoạt động: Demo Link
Có phản hồi? Hãy follow @upstash trên Twitter, và tham gia Discord server!