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

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

ในโพสต์นี้ ผมเล่าเกี่ยวกับการสร้าง real-time notifications ใช้ Server-Sent Events (SSE) ด้วย Upstash Redis, Next.js Server Actions และ Vercel การใช้ message channels ใน Upstash Redis สามารถเพิ่มประสิทธิภาพ communication architecture ของแอปพลิเคชันได้อย่างมาก ทำให้ responsive และ dynamic มากขึ้น

Demo

สิ่งที่เราจะใช้

  • Next.js (Front-end และ Back-end)
  • Upstash Redis (Server-Sent Events ด้วยคำสั่ง PUBLISH)
  • Tailwind CSS (Styling)
  • Vercel (Deployment)

สิ่งที่คุณต้องการ

  • Node.js 18
  • บัญชี Upstash
  • บัญชี Vercel

ตั้งค่า Upstash Redis

สร้างบัญชี Upstash login ไปที่แท็บ Redis สร้าง database

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

สร้างเสร็จ ไปแท็บ Details เลื่อนลงหา Connect your database คัดลอก Redis URL เก็บไว้เป็น UPSTASH_REDIS_URL

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

เลื่อนลงหา REST API กด .env คัดลอกเก็บไว้เป็น UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

ตั้งค่าโปรเจกต์

โคลน repo ติดตามคู่มือ:

git clone https://github.com/rishi-raj-jain/upstash-nextjs-publish-messages-with-sse-example
cd upstash-nextjs-publish-messages-with-sse-example
npm install

สร้าง .env ใส่ค่าจากข้างต้น:

# .env
# Upstash Redis Secrets
UPSTASH_REDIS_URL="rediss://default:...@...-...-...-....upstash.io:..."
UPSTASH_REDIS_REST_URL="https://...-...-...-....upstash.io"
UPSTASH_REDIS_REST_TOKEN="...="

สังเกต UPSTASH_REDIS_URL ใช้ "rediss" ไม่ใช่ "redis" เพื่อ TLS/SSL

รัน local:

npm run dev

โครงสร้าง Repository

โครงสร้างโฟลเดอร์หลัก (ไฟล์สีแดงจะอธิบายต่อ):

  • ทำความเข้าใจ Message Channels ใน Upstash Redis
  • สร้าง Server-Sent Events API ใน Next.js App Router
  • Setup Next.js Server Actions กับ Upstash Redis publish notifications
  • Setup Next.js frontend listen และ display notifications real-time

Real-Time Notifications ด้วย Upstash Redis, Next.js Server Actions และ Vercel: คู่มือทีละขั้นตอน

ทำความเข้าใจ Message Channels ใน Upstash Redis

ใน Upstash Redis publish/subscribe model เป็นหัวใจของ message channels Publishers broadcast messages ไป named channels subscribers รับ messages จาก channels เฉพาะแบบ real-time Model นี้ enable seamless communication ระหว่างส่วนต่างๆ ของแอปพลิเคชัน

Publish message ไป channel ด้วย @upstash/redis (Edge compatible):

import { Redis } from '@upstash/redis'
const redis = Redis.fromEnv()
await redis.publish('posts', JSON.stringify({
  date: new Date().toString(),
  message: "I am a new message."
}))

Subscribe channel posts ด้วย ioredis (Node compatible):

import Redis from 'ioredis'
const redisSubscriber = new Redis(process.env.UPSTASH_REDIS_URL)
const setKey = 'posts'
redisSubscriber.subscribe(setKey, (err) => { if (err) console.log(err) })
redisSubscriber.on('message', (channel, message) => {
  if (channel === setKey) console.log(message)
})

Publish message ไป channel → subscribers ทุกตัวรับ message ทันที real-time communication ใน Upstash Redis

สร้าง Server-Sent Events API ใน Next.js App Router

Server-Sent Events ส่ง data ใหม่ real-time โดยไม่ต้อง multiple client requests ต่างจาก request-response แบบเดิม SSE enable unidirectional flow จาก server ไป client ผ่าน single long-lived HTTP connection

Implement SSE ใน Next.js App Router:

// File: app/api/stream/route.js
export const dynamic = 'force-dynamic'
export async function GET() {
  const encoder = new TextEncoder()
  const customReadable = new ReadableStream({
    start(controller) {
      const message = 'Hey, I am a message.'
      controller.enqueue(encoder.encode(`data: ${message}\n\n`))
    },
  })
  return new Response(customReadable, {
    headers: {
      'Content-Type': 'text/event-stream; charset=utf-8',
      Connection: 'keep-alive',
      'Cache-Control': 'no-cache, no-transform',
      'Content-Encoding': 'none'
    },
  })
}

Publish Messages ไป Upstash Redis ด้วย Next.js Server Actions

Next.js Server Actions กำหนด server-side logic ใน frontend code ได้โดยตรง ประหยัดการสร้าง API Routes manual และ track form submission status

ใช้ use server ทำให้ function รันฝั่ง server ใน form submission Server Action ดึง message จาก form ใช้ Vercel Header ดึง country ของ user publish ไป channel posts ใน Upstash Redis

// File: app/actions.jsx
'use server'
import { Redis } from '@upstash/redis'
import { headers } from 'next/headers'

export async function publishNotification(formData) {
  'use server'
  const redis = Redis.fromEnv()
  const message = formData.get('message')
  const headersList = headers()
  const country = headersList.get('x-vercel-ip-country')
  await redis.publish(
    'posts',
    JSON.stringify({ message, country, date: new Date().toString() })
  )
}

Invoke Server Action ตอน submit form:

// File: app/page.jsx
import { publishNotification } from './actions'
const Home = () => {
  return (
    <>
      
{/* client side form component */}
) } export default Home

Setup Next.js Frontend แสดง Pending State ระหว่าง Form Submission ด้วย useFormStatus hook

Code ต่อไปสาธิต Next.js Form client component จัดการ form submission แสดง pending state ด้วย React useFormStatus hook:

  • Import useFormStatus hook จาก React ให้ status info ของ form submission ล่าสุด
  • ใช้ pending reactive variable บอกว่า form submission กำลังดำเนินการ
  • ถ้าไม่ pending reset form
  • ใช้ pending boolean แสดง conditional states ของ form
'use client'
import { useEffect } from 'react'
import { useFormStatus } from 'react-dom'
const Form = () => {
  const { pending } = useFormStatus()
  useEffect(() => {
    if (!pending) document.getElementById('publish-form').reset()
  }, [pending])
  return (
    <>
      
      
    
  )
}
export default Form

Setup Next.js Frontend ฟัง Server-Sent Events ตลอดเวลา

เรียนรู้ setup minimal listener สำหรับ SSE API messages และ approach persist connection ไป SSE API

ฟัง Server-Sent Events API ใน React Frontend

ใช้ useEffect hook สร้าง EventSource instance ชี้ไป /api/stream attach event listener สำหรับ message event parse data เป็น JSON process/display ใน component

Cleanup function ปิด SSE connection ตอน unmount ป้องกัน memory leaks

import { useEffect, useState } from 'react'
const ClientSideComponent = () => {
  useEffect(() => {
    const eventSource = new EventSource('/api/stream')
    eventSource.addEventListener('message', (event) => {
      const tmp = JSON.parse(event.data)
      // Do something with the obtained message
    })
    return () => { eventSource.close() }
  }, [])
  return <>
}
export default ClientSideComponent

Persist Connection ไป Server-Sent Events API ใน React Frontend

Enhanced version: mechanism ensure persistent connection SSE API โดย handle errors และ auto-reconnect

ผ่าน function connectToStream จัดการ establish และ maintain connection:

  1. Initial Connection และ Message Handling:
const connectToStream = () => {
  const eventSource = new EventSource('/api/stream')
  eventSource.addEventListener('message', (event) => {
    const tmp = JSON.parse(event.data)
    setPosts(prevPosts => [...prevPosts, tmp])
  })
  // ...
}
  1. Error Handling และ Automatic Reconnection:

Event listener สำหรับ error event ถ้า error (เช่น connection failure) ปิด event source setTimeout เรียก reconnect หลัง delay 1ms ช่วย smooth reconnection

eventSource.addEventListener('error', () => {
  eventSource.close()
  setTimeout(connectToStream, 1)
})
  1. จัดการ SSE API Source Closure:

ใช้ onclose detect ปิด source schedule reconnect หลัง brief delay

eventSource.onclose = () => {
  setTimeout(connectToStream, 1)
}

รวมกลยุทธ์เหล่านี้ connection SSE API persistent แม้ errors หรือ closures component reconnect ด้วย minimal delay

ปรับใช้งานบน Vercel

  • สร้าง GitHub Repository ด้วย app code
  • สร้าง New Project ใน Vercel Dashboard
  • Link GitHub Repository เป็น project ใหม่
  • Scroll ลง อัปเดต Environment Variables จาก .env local
  • Deploy! 🚀

อ้างอิง