Là full stack web framework, Remix cung cấp APIs xử lý các use case web server phổ biến. Bài này tập trung vào sessions và tại sao/cách dùng Upstash để lưu trữ chúng.
Session là gì?
Remix docs có giới thiệu rất tốt về sessions https://remix.run/docs/en/v1/api/remix#sessions
Vài từ - sessions là cơ chế cho phép server và client chia sẻ user data/state. Ví dụ: tracking authentication state, shopping cart status, flash messages, v.v.
Tại sao dùng Upstash Redis?
Session data lưu trên server. Khi deploy lên serverless infrastructure hoặc PAAS (Heroku), bạn không thể persist data trên file system của server vì nó có thể thay đổi mỗi request (serverless) hoặc mỗi deployment (PAAS). Để persist data, bạn nên lưu user data trên external database. Upstash Redis DB là giải pháp tuyệt vời vì:
- Giống Redis, Sessions bản chất có cấu trúc
key:valuenơi key là session id và value là serialized data. - Redis có cơ chế hết hạn built-in giảm nhu cầu cleanup expired sessions.
- Session có thể chứa sensitive user data và Upstash Redis mã hóa tất cả dữ liệu lưu trữ.
- Upstash dùng HTTP REST API đơn giản. HTTP là cách dễ nhất giao tiếp từ serverless infrastructure.
Cách dùng Upstash làm session provider trong Redis
Bài này dựa trên Redis Session Storage Using Upstash Example tôi viết. Clone Remix repo và thử đi.
Bước 1 - Lấy Upstash API keys
- Vào https://upstash.com/ tạo tài khoản mới
- Tạo Redis DB mới
- Sao chép
UPSTASH_REDIS_REST_URL&UPSTASH_REDIS_REST_TOKENlưu vào file.envở root Remix project. - Cài
dotenv-$ npm install --save-dev dotenv- Cho phép inject env variables từ file.envvừa tạo. - Vào
package.jsonthay scriptdevtừremix devthànhdotenv/config node_modules/.bin/remix dev.
Bước 2 - Triển khai core createSessionStorage tạo Upstash session implementation
Remix cung cấp API tuyệt vời build Session integration dùng createSessionStorage. Triển khai function này để tích hợp Upstash.
// sessions/upstash.server.ts
import * as crypto from "crypto";
import { createSessionStorage } from "remix";
const upstashRedisRestUrl = process.env.UPSTASH_REDIS_REST_URL;
const headers = {
Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`,
Accept: "application/json",
"Content-Type": "application/json",
};
const expiresToSeconds = (expires) => {
const now = new Date();
const expiresDate = new Date(expires);
const secondsDelta = expiresDate.getSeconds() - now.getSeconds();
return secondsDelta < 0 ? 0 : secondsDelta;
};
// Xem thêm https://remix.run/docs/en/v1/api/remix#createsessionstorage
export function createUpstashSessionStorage({ cookie }: any) {
return createSessionStorage({
cookie,
async createData(data, expires) {
// Tạo random id - lấy từ core `createFileSessionStorage` Remix function.
const randomBytes = crypto.randomBytes(8);
const id = Buffer.from(randomBytes).toString("hex");
// Gọi Upstash Redis HTTP API. Set expiration theo cookie `expired property.
// Lưu ý dùng `expiresToSeconds` chuyển date sang giây.
await fetch(
`${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
{
method: "post",
body: JSON.stringify({ data }),
headers,
}
);
return id;
},
async readData(id) {
const response = await fetch(`${upstashRedisRestUrl}/get/${id}`, { headers });
try {
const { result } = await response.json();
return JSON.parse(result).data;
} catch (error) {
return null;
}
},
async updateData(id, data, expires) {
await fetch(
`${upstashRedisRestUrl}/set/${id}?EX=${expiresToSeconds(expires)}`,
{
method: "post",
body: JSON.stringify({ data }),
headers,
}
);
},
async deleteData(id) {
await fetch(`${upstashRedisRestUrl}/del/${id}`, {
method: "post",
headers,
});
},
});
}
Giải thích: Tạo file sessions/upstash.server.ts. Export function createUpstashSessionStorage. Function nhận parameter cookie (xem sau) và dùng core Remix createSessionStorage factory function implement session provider mới.
Bên trong function triển khai protocol createSessionStorage cho tạo session mới (createData), đọc session value (readData), cập nhật session value (updateData) và xóa session (deleteData).
Mỗi function dùng REST Upstash API tương tác với Redis Database.
Lưu ý:
- Cookie truyền vào chứa
cookie expiration datedạngjs Date. DùngexpiresToSecondschuyển sang giây - thứ Redis mong đợi. - Khi set cookie, đừng quên set expiration date. Redis tự xóa session khi hết hạn.
- Dùng
cryptotạo unique session id. Có thể dùng phương án khác. Tôi chọn cách này vì giống corecreateFileSessionStoragefunction.
Bước 3 - Dùng createSessionStorage trên app
Đã tạo session storage implementation, demo cách dùng.
Lưu ý từ đây không có gì đặc thù Upstash. Tất cả logic đóng gói trong
sessions/upstash.server.ts.
// sessions.server.ts
import { createCookie } from "remix";
import { createUpstashSessionStorage } from "~/sessions/upstash.server";
// Set độ dài session. Ví dụ dùng duration rất ngắn để demo dễ dàng.
const EXPIRATION_DURATION_IN_SECONDS = 10;
const expires = new Date();
expires.setSeconds(expires.getSeconds() + EXPIRATION_DURATION_IN_SECONDS);
const sessionCookie = createCookie("__session", {
secrets: ["r3m1xr0ck1"],
sameSite: true,
expires,
});
const { getSession, commitSession, destroySession } = createUpstashSessionStorage({ cookie: sessionCookie });
export { getSession, commitSession, destroySession };
Tạo file sessions.server.ts paste code trên. File export 3 functions getSession, commitSession, destroySession. Cho phép app tương tác với session. Cũng tạo cookie lưu reference đến session ở client.
Setup expiration duration theo business needs. Xem thêm MDN Cookies.
Sử dụng session trong Remix routes
Với Remix, định nghĩa session usage per route. Ví dụ dùng session trên routes/index.tsx. Chỉ demo cách dùng session API. Kết nối session với business logic cụ thể ngoài phạm vi bài này.
Nếu tìm ví dụ dùng session cho authentication xem đây
// routes/index.tsx
import type { LoaderFunction } from "remix";
import { json, useLoaderData } from "remix";
import { commitSession, getSession } from "~/sessions.server";
export const loader: LoaderFunction = async ({ request }) => {
// Lấy session từ cookie
const session = await getSession(request.headers.get("Cookie"));
const myStoredData = session.get("myStoredData");
// Nếu không tìm thấy session (chưa tạo hoặc hết hạn) tạo session mới.
if (!myStoredData) {
session.set("myStoredData", "Some data");
return json(
{ message: "Created new session" },
{ headers: { "Set-Cookie": await commitSession(session) } }
);
}
// Nếu session tìm thấy, hiển thị session info.
return json({ message: `Showing Session info: ${myStoredData}` });
};
export default function () {
const data = useLoaderData();
return <div>{data.message}</div>;
}
Ví dụ demo xử lý 2 trạng thái session (User có session hoặc không). Nếu không có session, truy cập index sẽ tạo session mới lưu dummy data. Nếu có session hợp lệ (chưa hết hạn) route sẽ show session data.
Bước 4 - Deploy
Sau khi implement session dùng Upstash, bạn tự do chọn deployment strategy bất kỳ.
Đừng quên set Upstash environment variables.
Phụ lục
Dùng createFileSessionStorage cho local development và createUpstashSessionStorage cho staging/production
Có thể muốn develop offline. Khi develop local, thay createUpstashSessionStorage bằng createFileSessionStorage bằng cách detect NODE_ENV.
Sau khi test Upstash implementation works, sửa sessions/upstash.server.ts:
Thay đoạn này:
// from sessions/upstash.server.ts
const { getSession, commitSession, destroySession } = createUpstashSessionStorage({ cookie: sessionCookie });
Bằng:
// from sessions/upstash.server.ts
const { getSession, commitSession, destroySession } = (process.env.NODE_ENV === "development")
? createFileSessionStorage({ cookie: sessionCookie, dir: './sessions' })
: createUpstashSessionStorage({ cookie: sessionCookie });
Bây giờ develop local sẽ dùng local file system thay vì gọi Upstash.
Kết luận
Trong bài này chúng ta thấy cách dùng Upstash Redis DB host session storage data. Remix API đóng gói cụ thể session storage implementation rất tốt, tích hợp straight forward. Muốn chơi với ví dụ, check ở Remix source code đây.
Enjoy.