Ngày nay, khó tưởng tượng thế giới IT không có Redis database. Trong Stackoverflow Developer Survey 2021, in-memory database được ưa chuộng và được hơn 70.000 developer chọn là database được yêu thích nhất. Là in-memory database, Redis mang lại hiệu suất mạnh, lý tưởng cho các tình huống yêu cầu thời gian phản hồi ngắn và độ trễ tối thiểu. Tuy nhiên, use case của Redis thường bị hiểu nhầm chỉ giới hạn ở caching và message-brokering. Hôm nay chúng ta sẽ xem tại sao điều này sai và dùng Redis vai trò primary database.
Ý tưởng
Chúng ta muốn xây dựng app nhỏ cho phép user để lại feedback dưới dạng khảo sát trực tuyến. Cụ thể, giả sử thu thập feedback cho một công ty. Để đơn giản, tập trung vào chức năng:
- User trả lời 3 câu hỏi:
- Cảm thấy thế nào về sản phẩm/dịch vụ? 1 - 10 điểm
- Có giới thiệu cho đồng nghiệp không? Yes/No (true/false)
- Chia sẻ suy nghĩ... Free text
- User submit form.
- Mỗi kết quả khảo sát lưu như một record đơn lẻ trong DB (hash).
- User có thể xem kết quả khảo sát.
Xem demo app.
Tech Stack
App khảo sát nhỏ là ví dụ hoàn hảo tận dụng tối đa kiến trúc serverless. Serverless đảm bảo scalability tối đa chi phí tối thiểu với công nghệ:
Next.js
Next.js là open-source framework nâng cao React truyền thống với server-side rendering, static page generation và quan trọng nhất API routes. Dùng Next.js tạo cả frontend và API.
Upstash Redis
Upstash cung cấp Redis database full-serverless, persistent, dễ dùng, giá per-request rất thấp. Xây dựng trên Redis truyền thống, Upstash kết hợp hiệu suất không thể đánh bại của Redis với độ bền của disk storage - hoàn hảo cho use case này.
Thiết lập dự án
- Tạo Next.js app:
npx create-next-app survey-app. - Tạo Upstash Redis database trong Upstash console và sao chép UPSTASH_REDIS_REST_URL và UPSTASH_REDIS_REST_TOKEN.
Dự án là single page app với 2 API endpoints:
pages/api/submit.jslưu survey entrypages/api/results.jslấy tất cả survey entries
Để giao tiếp với Upstash dễ dàng, cài @upstash/redis npm package qua npm install @upstash/redis.
Mã nguồn
Tạo file pages/api/submit.js:
// pages/api/submit.js
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: "INSERT_YOUR_URL_HERE",
token: "INSERT_YOUR_TOKEN_HERE",
});
const submitHandler = async (req, res) => {
const body = req.body;
// Chuẩn bị data insert vào DB
const data = {
rating: String(body.rating) || "0",
recommendation: String(body.recommendation) || "false",
comment: String(body.comment) || "",
};
// Tạo random id lưu survey entry
const id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
// Insert vào Upstash redis
try {
// Store survey data
await redis.hset(`entries:${id}`, data);
// Store id survey để retrieve sau
await redis.sadd("entries", `entries:${id}`);
} catch (error) {
console.error("Failed to insert data into redis", error);
return res.status(500).json({
success: false,
message: "Failed to insert data into redis",
});
}
return res.status(200).json({
success: true,
message: "Data inserted successfully",
});
};
export default submitHandler;
Làm 3 việc ở đây:
- Lấy survey data từ request body và chuẩn bị cho Redis
- Insert survey entry vào Redis như hash
- Thêm id của survey entry vào set
Bạn có thể thắc tại sao tạo hash cho survey entry rồi lại thêm id vào set. Bước này quan trọng khi retrieve event từ Redis. Redis hoạt động như key-value store, không tìm data trừ khi chỉ định key chính xác. Query như SELECT * FROM SurveyResults; được hỗ trợ trong SQL nhưng Redis phải dùng trick khác. Tạo set thêm tất cả Redis keys của survey result entries vào. Khi retrieve all survey entries, chỉ cần lookup keys trong set. Quay lại code xem thực tế như thế nào.
Tạo file pages/api/results.js:
// pages/api/results.js
import { Redis } from "@upstash/redis";
const resultsHandler = async (req, res) => {
// Retrieve data from redis
const redis = new Redis({
url: "INSERT_YOUR_URL_HERE",
token: "INSERT_YOUR_TOKEN_HERE",
});
try {
// Tìm tất cả entries trong set
const entries = await redis.smembers("entries");
// Lấy all survey entries bằng id/key
// Chạy multiple queries cùng lúc, Upstash hỗ trợ pipeline command. Chạy nhiều queries cùng lúc và lấy kết quả trong một call.
const p = redis.pipeline();
entries.forEach((id) => {
p.hgetall(id);
});
const results = await p.exec();
return res.status(200).json({
success: true,
message: "Data retrieved successfully",
data: results,
});
} catch (error) {
console.error("Failed to retrieve data from redis", error);
return res.status(500).json({
success: false,
message: "Failed to retrieve data from redis",
});
}
};
export default resultsHandler;
Backend đã hoạt động, hoàn thiện app với frontend.
Tạo file pages/index.js:
// pages/index.js
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
export default function Home() {
const handleSubmit = async (e) => {
e.preventDefault();
const form = e.target;
const data = {
rating: form.rating.value,
recommendation: form.recommendation.value,
comment: form.comment.value,
};
// gửi data đến backend
await fetch("/api/submit", {
body: JSON.stringify(data),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "POST",
});
alert("Thank you for your feedback!");
};
const RatingOption = ({ value }) => (
<div>
<input type="radio" name="rating" value={value} required />{" "}
<label>{value}</label>
</div>
);
return (
<div className={styles.container} onSubmit={handleSubmit}>
<form>
<div>
<label>How do you feel about our products/services?</label>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((value) => (
<RatingOption key={value} value={value} />
))}
</div>
<div>
<label>Would you recommend us to your colleagues?</label>
<div>
<input type="radio" name="recommendation" value="true" required />{" "}
<label>Yes</label>
</div>
<div>
<input type="radio" name="recommendation" value="false" required />{" "}
<label>No</label>
</div>
</div>
<div>
<label>Please share your thoughts... (Optional)</label>
<textarea name="comment" placeholder="This is what I liked most/this is what you can improve..."></textarea>
</div>
<input type="submit" />
</form>
</div>
);
}
Để style hoạt động, thay nội dung styles/Home.module.css:
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.container form > div {
padding: 20px;
display: flex;
flex-direction: column;
align-items: stretch;
}
.container form > div > label {
margin-bottom: 10px;
}
Sẵn sàng nhận survey entries! Nhưng còn việc implement survey results frontend.
Tạo file pages/results.js:
import { useEffect, useState } from "react";
import styles from "../styles/Results.module.css";
export default function Results() {
const [surveyData, setSurveyData] = useState([]);
useEffect(() => {
fetch("/api/results")
.then((res) => res.json())
.then((response) => setSurveyData(response.data));
}, []);
return (
<div className={styles.container}>
{" "}
{surveyData.map((data) => (
<div key={data.id}>
<p> <strong> Rating: </strong> {data.rating}{" "} </p>{" "}
<p> <strong> Recommendation: </strong> {data.recommendation}{" "} </p>{" "}
<p> <strong> Comment: </strong> {data.comment}{" "} </p>{" "}
</div>
))}{" "}
</div>
);
}
Tạo file styles/Results.module.css:
.container {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
min-height: 100vh;
margin: 50px 0;
}
.container > div {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.container p { margin: 0; }
Tìm thấy tổng quan tất cả survey entries tại localhost:3000/results.
Mã nguồn hoàn chỉnh tại GitHub repo upstash-survey-app.
Kết luận
Trong bài này chúng ta phát triển Next.js fullstack web app xử lý form entries và lưu form đã điền trong Upstash serverless Redis. Thấy cách Redis dùng làm primary database và thay đổi thiết kế khi chuyển từ SQL sang Redis.
Với serverless Redis database dễ setup, Upstash làm việc cực kỳ dễ dàng để lưu form data lên cloud.
Hy vọng bài viết giúp bạn hiểu Redis, cảm nhận Upstash Redis và bắt đầu xây dựng ứng dụng với khả năng lưu trữ dữ liệu mới.