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

สร้างรายการสิ่งที่ต้องทำด้วย Blitz.js &Redis

Blitz.js เป็นเฟรมเวิร์ก React ที่เดิมแยกจาก Next.js วันนี้เราจะสร้างแอปพลิเคชั่น To-Do ของ Blitz.js ที่จัดเก็บงานใน Upstash เพื่อไม่ให้เป็นการเสียเวลา มาเริ่มกันเลย!

ตั้งค่า

คุณจะต้องติดตั้ง Blitz.js บนคอมพิวเตอร์ของคุณเพื่อเริ่มต้น

NPM:

npm install -g blitz --legacy-peer-deps

เส้นด้าย:

yarn global add blitz

หากต้องการสร้างแอป Blitz.js ใหม่ ให้ใช้ blitz new และ cd ลงในไดเร็กทอรี

blitz new blitzjs-todo && cd blitzjs-todo

เยี่ยมมาก ตอนนี้มาติดตั้ง TailwindCSS เพื่อจัดรูปแบบเว็บไซต์ของเรา

blitz install tailwind

สุดท้าย มาติดตั้ง Upstash JS SDK เพื่อให้การเรียก Upstash API ง่ายขึ้น

NPM:

npm i @upstash/redis

เส้นด้าย:

yarn i @upstash/redis

ณ จุดนี้ คุณควรเรียกใช้ blitz dev เพื่อให้แน่ใจว่าทุกอย่างทำงานอย่างถูกต้อง ลองสร้างบัญชีและลงชื่อเข้าใช้ด้วย ควรมีลักษณะเช่นนี้หากคุณทำทุกอย่างถูกต้องแล้ว

สร้างรายการสิ่งที่ต้องทำด้วย Blitz.js &Redis

นอกจากนี้ โครงสร้างไฟล์ของคุณควรมีลักษณะดังนี้:

สร้างรายการสิ่งที่ต้องทำด้วย Blitz.js &Redis

คัดลอก UPSTASH_REDIS_REST_URL . เหล่านี้ และ UPSTASH_REDIS_REST_TOKEN จาก Upstash Console ไปยังไฟล์ชื่อ .env สำหรับตอนนี้. ควรมีลักษณะดังนี้:

# 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_HERE

เราได้ตั้งค่าแอปพลิเคชัน Blitz.js ของเราเรียบร้อยแล้ว! มาเริ่มใช้งานรายการสิ่งที่ต้องทำของเรากันเถอะ

การนำไปใช้

Blitz.js มาพร้อมกับ User Authentication ในตัว! มาใช้ประโยชน์จากสิ่งนี้เพื่อสร้างรายการสิ่งที่ต้องทำส่วนตัวสำหรับผู้ใช้แต่ละคน

ขั้นแรก ให้เริ่มต้น Upstash JS SDK ใน /lib/redis.ts

import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
export default redis;

เราจะต้องสร้าง 3 เส้นทาง API ต่างๆ เพื่อเข้าถึงรายการสิ่งที่ต้องทำของเรา

ไปที่ 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;

มาดูกันว่าเส้นทาง API นี้ทำงานอย่างไรทีละขั้นตอน ขั้นแรก เราขอเส้นทาง บนเส้นทางนั้น เราตรวจสอบว่าผู้ใช้เข้าสู่ระบบแล้ว หากไม่มีผู้ใช้ เราจะตอบกลับว่า "ไม่ได้รับอนุญาต" หากมี คือ ผู้ใช้ จากนั้นเราดึงฐานข้อมูล Upstash Redis เพื่อค้นหาสิ่งที่ต้องทำทั้งหมดที่อยู่ในรายการในปัจจุบัน สิ่งนี้จะดึงสิ่งที่ต้องทำประมาณร้อยรายการ

ถาม:เดี๋ยวก่อน เราจะเพิ่ม To-Dos ได้อย่างไร A:เป็นคำถามที่ดี! ไปทำกันเลย!

อีกครั้ง ให้วางโค้ดต่อไปนี้ลงในไฟล์ใหม่ชื่อ add.ts ใน app/api .

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 นี้ค่อนข้างคล้ายกับเส้นทางสุดท้าย แต่สังเกตว่าเราได้เพิ่มการตรวจสอบเพิ่มเติมในบรรทัดที่ห้า นั่นเป็นเพราะคำขอนี้ไม่ใช่ GET ขอค่อนข้างเป็น POST ขอ. สังเกตวิธีที่เราตรวจสอบสามสิ่ง อันดับแรก เราตรวจสอบให้แน่ใจว่าคำขอนั้นเป็น POST ขอ. ต่อไป เราตรวจสอบให้แน่ใจว่ามี JSON หรือข้อความใน req.body.data . สุดท้าย เราตรวจสอบให้แน่ใจว่าผู้ใช้เข้าสู่ระบบแล้ว หากผ่านการตรวจสอบเพียงเล็กน้อย เราสามารถพุชสิ่งที่ต้องทำไปยังรายการ Redis ของเราบน Upstash หากมีข้อผิดพลาดขณะดึงข้อมูล เราสามารถคืนค่า 500 โดยใช้ .catch .

เส้นทางสุดท้ายที่เราต้องเพิ่มคือเส้นทางหนึ่งเพื่อลบสิ่งที่ต้องทำของเรา เมื่อคุณทำบางสิ่งเสร็จแล้ว คุณต้องข้ามมันออกไปแน่นอน! มาเพิ่มเส้นทาง API สุดท้ายของเราใน 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;

สังเกตเห็นสิ่งที่คล้ายกัน? นั่นเป็นเพราะว่าเส้นทางนี้เกือบจะเหมือนกับ add เส้นทาง API ความแตกต่างที่สำคัญที่นี่คือ เรากำลังใช้ LREM ไม่ใช่ LPUSH เพื่อลบรายการออกจาก Redis

การสร้างส่วนหน้า

เริ่มจากลบทุกอย่างใน app/pages/index.js และเขียนรายการสิ่งที่ต้องทำของเราทีละขั้นตอน

วางการนำเข้าเหล่านี้ที่ด้านบนของไฟล์

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";

เราจะใช้ React Hooks เพื่อสร้างฟังก์ชันหลักของรายการสิ่งที่ต้องทำ มาปรับใช้คุณสมบัติหลักบางอย่างของรายการกัน

const Main = () => {
  const todoRef = useRef<HTMLInputElement>(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 (
      <>
        <button
          className="mt-4 px-2 py-1 border-2 border-black hover:bg-gray-400 mb-3"
          onClick={async () => {
            await logoutMutation()
          }}
        >
          Logout
        </button>
        <div>
          User id: <code>{currentUser.id}</code>
          <br />
          User email: <code>{currentUser.email}</code>
        </div>
        <form className="mt-2" onSubmit={handleAddTodo}>
          <p>add a todo:</p>
          <input
            ref={todoRef}
            className="w-full border-black border-2 focus:outline-none text-center"
          />
        </form>
        <div className="flex flex-col gap-2 mt-4 bg-gray-300 rounded-md">
          {(todos as string[]).map((todo: string, index: number) => (
            <div className="flex items-center p-3 rounded-md bg-gray-300" key={index}>
              <button
                onClick={() => handleRemoveTodo(todo)}
                className="flex items-center mr-4 justify-center w-5 h-5 rounded-[0.25rem] border border-solid border-gray-500 shadow-sm hover:bg-gray-700"
              ></button>

              <span>{todo}</span>
            </div>
          ))}
        </div>
      </>
    )
  } else {
    return (
      <div className="flex flex-col gap-4 text-center">
        <Link href={Routes.SignupPage()}>
          <a className="mt-4 px-2 py-1 border-2 border-black hover:bg-gray-400">
            <strong>Sign Up</strong>
          </a>
        </Link>
        <Link href={Routes.LoginPage()}>
          <a className="mt-4 px-2 py-1 border-2 border-black hover:bg-gray-400">
            <strong>Login</strong>
          </a>
        </Link>
      </div>
    )
  }
}

<Main/> องค์ประกอบที่เป็นแกนหลักของแอปพลิเคชันของเรา การดูโค้ดอย่างละเอียดถี่ถ้วนจะบอกเราว่าเราใช้งานอย่างไร ที่ด้านบนขององค์ประกอบ เราเริ่มต้นสถานะสำหรับแอปพลิเคชันของเรา นอกจากนี้เรายังประกาศ ref เพื่อใช้ในภายหลังในการป้อนข้อมูล "สิ่งที่ต้องทำใหม่" ของเรา คุณอาจสังเกตเห็นการใช้ antiCSRFToken ! Blitz.js ต้องใช้โทเค็นเหล่านี้เมื่อดึงเส้นทาง API ใด ๆ เพื่อป้องกันไม่ให้ผู้ประสงค์ร้ายทำอันตรายไซต์ของคุณ เป็นเรื่องที่ดีในความคิดของฉัน!

เราใช้สามฟังก์ชันหลักในการจัดการข้อมูลของเราบนเว็บไซต์ สามสิ่งนี้คือ:

  • handleAddTodo
  • handleRemoveTodo
  • fetchTodos

เราเรียก fetchTodos ทันทีที่หน้าโหลดขึ้นมา ให้โหลด To-Dos ทั้งหมดที่ผู้ใช้ยังต้องทำให้เสร็จ เมื่อผู้ใช้ลบหรือเพิ่มสิ่งที่ต้องทำ เราจะเรียก fetchTodos อีกครั้งเพื่อสะท้อนถึงการเปลี่ยนแปลงนั้นบนเว็บไซต์!

หากผู้ใช้ไม่ได้เข้าสู่ระบบ ผู้ใช้จะได้รับแจ้งให้ลงชื่อเข้าใช้เว็บไซต์ก่อนที่จะเห็นหน้านี้

คุณสามารถลงทะเบียนหรือเข้าสู่ระบบหากคุณยังไม่มีเซสชั่นบนเว็บไซต์ โปรดจำไว้ว่า คุณไม่สามารถจัดเก็บสิ่งที่ต้องทำโดยไม่มีบัญชี และเส้นทาง API ทั้งหมดกำหนดให้คุณต้องตรวจสอบสิทธิ์ด้วย AntiCSRFToken !

แต่เดี๋ยวก่อน อีกหนึ่งขั้นตอนที่สำคัญ! เราต้องส่งออกหน้า!

const Home: BlitzPage = () => {
  return (
    <div className="flex flex-col min-h-screen items-center justify-center">
      <main>
        <div className="my-4">
          <Suspense fallback="Loading...">
            <Main />
          </Suspense>
        </div>
      </main>
    </div>
  );
};

Home.suppressFirstRenderFlicker = true;
Home.getLayout = (page) => <Layout title="Home">{page}</Layout>;

export default Home;

ดังที่คุณเห็นด้านบน Blitz.js ใช้แนวทางที่แตกต่างจาก Next เล็กน้อย แต่ในตอนนี้แนวทางดังกล่าวยังคงเหมือนเดิม เราใช้ Suspense ที่เรานำเข้ามาก่อนหน้านี้เพื่อแสดงให้ผู้ใช้เห็นว่าแอปกำลังโหลด จากนั้นเราจะแสดง </Main> . ของเรา คอมโพเนนท์หลังจากโหลดเสร็จ!

หากต้องการดูผลการเปลี่ยนแปลงของคุณ ให้เรียกใช้ในคอนโซลอีกครั้ง และไปที่แอปของคุณในเบราว์เซอร์

blitz dev

หากคุณปฏิบัติตามคำแนะนำ ใบสมัครของคุณควรมีลักษณะเป็นแนวนี้เมื่อคุณเข้าสู่ระบบและเพิ่มสิ่งที่ต้องทำ:

สร้างรายการสิ่งที่ต้องทำด้วย Blitz.js &Redis

คุณสามารถลบ To-Do ได้โดยคลิกที่ช่องข้างๆ นั่นคือสิ่งที่ removeTodo ฟังก์ชันสำหรับ 😉.

ยินดีด้วย!

ฉันหวังว่าคุณจะได้เรียนรู้สิ่งใหม่จากการอ่านโพสต์ในบล็อกนี้ และถ้าคุณไม่ จำไว้ว่าการฝึกฝนทักษะของคุณก็ไม่เสียหาย! Blitz.js กำลังเปลี่ยนโฟกัสออกจาก Next.js ดังนั้นมันอาจจะเป็นเฟรมเวิร์กที่แตกต่างไปจากเดิมอย่างสิ้นเชิงในอนาคต แต่โปรดติดตามเว็บไซต์ของพวกเขาที่นี่!

ที่มาของโครงการ :ลิงค์ GitHub

การสาธิตการทำงาน: ลิงค์สาธิต

มีข้อเสนอแนะ? อย่าลืมติดตาม @upstash บน Twitter และเข้าร่วมเซิร์ฟเวอร์ Discord!