Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

ปรับปรุง Remix Sessions ด้วย Upstash Redis: Session Store ที่ปลอดภัยและขยายได้

Remix** เป็น Full Stack Framework ที่ให้ API จัดการ Session ได้ในตัว บทความนี้จะสอนใช้ **Upstash Redis** เป็น Session Store แทน File System

Session คืออะไร?

กลไกให้ Server และ Client แชร์ State/User Data เช่น Authentication State, Shopping Cart, Flash Messages

Remix Docs: Session Documentation

ทำไมต้อง Upstash Redis?

  • Key-Value Nature: Session = Key (Session ID) : Value (Serialized Data) - เข้ากันได้กับ Redis สุดๆ
  • Built-in Expiration: Redis มี TTL อัตโนมัติ - ไม่ต้อง Cleanup Expired Sessions เอง
  • Encryption at Rest: Upstash เข้ารหัสข้อมูลใน Disk - ปลอดภัยสำหรับ Sensitive Data
  • HTTP REST API: ใช้จาก Serverless/Edge Functions ได้ง่าย (ไม่ต้อง TCP Connection)

Implementation Steps

Step 1: Setup Upstash Credentials

# 1. สร้าง DB ที่ https://upstash.com/
# 2. Copy UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN ใส่ .env
# 3. Install dotenv
npm install --save-dev dotenv

# 4. Update package.json dev script
"dev": "dotenv/config node_modules/.bin/remix dev"

Step 2: Implement createSessionStorage (sessions/upstash.server.ts)

// 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: Date) => {
  const now = new Date();
  const expiresDate = new Date(expires);
  const secondsDelta = (expiresDate.getTime() - now.getTime()) / 1000;
  return secondsDelta < 0 ? 0 : Math.floor(secondsDelta);
};

export function createUpstashSessionStorage({ cookie }: any) {
  return createSessionStorage({
    cookie,
    async createData(data, expires) {
      const randomBytes = crypto.randomBytes(8);
      const id = Buffer.from(randomBytes).toString("hex");

      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 {
        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 });
    },
  });
}

Key Points:

  • Cookie มี `expires` เป็น JS Date → แปลงเป็นวินาทีให้ Redis (`expiresToSeconds`)
  • ใช้ `crypto.randomBytes` สร้าง Session ID (เหมือน Core Remix)
  • Redis จะ Auto-delete Expired Keys

Step 3: ใช้งานใน App (sessions.server.ts)

// sessions.server.ts
import { createCookie } from "remix";
import { createUpstashSessionStorage } from "~/sessions/upstash.server";

const EXPIRATION_DURATION_IN_SECONDS = 10; // Demo: 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 };

Step 4: ใช้ Session ใน Route (routes/index.tsx)

// 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 }) => {
  const session = await getSession(request.headers.get("Cookie"));
  const myStoredData = session.get("myStoredData");

  if (!myStoredData) {
    // ไม่มี Session → สร้างใหม่
    session.set("myStoredData", "Some data");
    return json(
      { message: "Created new session" },
      { headers: { "Set-Cookie": await commitSession(session) } }
    );
  }

  // มี Session → แสดงข้อมูล
  return json({ message: `Showing Session info: ${myStoredData}` });
};

export default function() {
  const data = useLoaderData();
  return <div>{data.message}</div>;
}

Step 5: Deploy

Deploy ไป Vercel/Netlify/Cloudflare Pages ได้เลย แค่ตั้ง Environment Variables ใน Platform

Appendix: Local Development vs Production

พัฒนา Local ใช้ File System, Production ใช้ Upstash:

// sessions.server.ts
import { createFileSessionStorage } from "remix";

const { getSession, commitSession, destroySession } = process.env.NODE_ENV === "development"
  ? createFileSessionStorage({ cookie: sessionCookie, dir: "./sessions" })
  : createUpstashSessionStorage({ cookie: sessionCookie });

สรุป

  • Remix `createSessionStorage` ทำให้ Custom Session Store เขียนง่าย
  • Upstash Redis เหมาะสำหรับ Session: Key-Value, TTL, Encryption, HTTP API
  • Deploy ไป Serverless/Edge ไหนก็ได้

Example Repo: Remix Redis Upstash Session Example