Computer >> Máy Tính >  >> Lập trình >> Redis

Xây dựng danh sách việc cần làm với Blitz.js &Redis

Blitz.js là một khung công tác React ban đầu được phân nhánh từ Next.js. Hôm nay, chúng tôi sẽ xây dựng một ứng dụng Blitz.js To-Do để lưu trữ các công việc trong Upstash. Không cần thêm lời khuyên nào nữa, hãy bắt đầu!

Thiết lập

Bạn cần cài đặt Blitz.js trên máy tính của mình để bắt đầu.

NPM:

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

Sợi:

yarn global add blitz

Để tạo ứng dụng Blitz.js mới, hãy sử dụng blitz new và cd vào thư mục.

blitz new blitzjs-todo && cd blitzjs-todo

Tuyệt vời, bây giờ hãy cài đặt TailwindCSS để tạo kiểu cho trang web của chúng tôi.

blitz install tailwind

Cuối cùng, hãy cài đặt Upstash JS SDK để thực hiện các lệnh gọi tới API Upstash dễ dàng hơn.

NPM:

npm i @upstash/redis

Sợi:

yarn i @upstash/redis

Tại thời điểm này, bạn nên chạy blitz dev để đảm bảo mọi thứ hoạt động chính xác. Hãy thử tạo tài khoản và đăng nhập nữa. Nó sẽ trông giống như thế này nếu bạn đã làm mọi thứ một cách chính xác cho đến nay.

Xây dựng danh sách việc cần làm với Blitz.js &Redis

Ngoài ra, cấu trúc tệp của bạn sẽ trông như thế này:

Xây dựng danh sách việc cần làm với Blitz.js &Redis

Sao chép UPSTASH_REDIS_REST_URL này và UPSTASH_REDIS_REST_TOKEN từ Upstash Console vào tệp có tên .env cho bây giờ. Nó sẽ trông như thế này:

# 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

Chúng tôi đã thiết lập hoàn toàn ứng dụng Blitz.js của mình ngay bây giờ! Hãy bắt đầu triển khai danh sách Việc cần làm của chúng tôi.

Triển khai

Blitz.js đi kèm với Xác thực Người dùng được tích hợp sẵn! Hãy tận dụng điều này để tạo danh sách việc cần làm riêng tư cho từng người dùng.

Đầu tiên, hãy khởi chạy Upstash JS SDK trong /lib/redis.ts

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

Chúng tôi sẽ cần thực hiện 3 Các tuyến API khác nhau để truy cập danh sách Việc cần làm của chúng tôi.

Điều hướng đến app/api và tạo một tệp có tên getall.ts . Khi bạn đã làm như vậy, hãy dán mã sau vào:

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;

Hãy xem qua cách hoạt động của Tuyến API này, từng bước. Đầu tiên, chúng tôi yêu cầu tuyến đường. Trên chính lộ trình, chúng tôi xác nhận rằng người dùng đã đăng nhập. Nếu không có người dùng nào, chúng tôi trả về phản hồi "Không được phép". Nếu có người dùng, sau đó chúng tôi tìm nạp cơ sở dữ liệu Upstash Redis của mình để tìm tất cả Việc cần làm hiện có trong danh sách. Điều này sẽ tìm nạp khoảng một trăm việc cần làm.

H:Chờ đã, chúng ta phải thêm Việc cần làm ngay từ đầu như thế nào? A:Câu hỏi hay! Hãy làm điều đó tiếp theo!

Một lần nữa, hãy dán mã sau vào một tệp mới có tên add.ts trong 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;

Tuyến API này khá giống với tuyến cuối cùng, nhưng lưu ý rằng chúng tôi đã thêm nhiều kiểm tra hơn trong dòng thứ năm. Đó là bởi vì yêu cầu này không phải là GET thay vào đó, nó là một POST lời yêu cầu. Lưu ý cách chúng tôi kiểm tra ba điều. Trước tiên, chúng tôi đảm bảo rằng yêu cầu thực sự là một POST lời yêu cầu. Tiếp theo, chúng tôi đảm bảo rằng có JSON hoặc văn bản trong req.body.data . Cuối cùng, chúng tôi đảm bảo rằng người dùng đã đăng nhập. Nếu tất cả các bước kiểm tra nhỏ này được thông qua, chúng tôi có thể đẩy Việc cần làm của mình vào danh sách Redis của chúng tôi trên Upstash. Nếu có bất kỳ loại lỗi nào trong khi tìm nạp, chúng tôi có thể trả về 500 bằng cách sử dụng .catch .

Lộ trình cuối cùng chúng ta cần thêm là một lộ trình để xóa Việc cần làm của chúng ta. Một khi bạn hoàn thành điều gì đó, tất nhiên bạn phải gạch bỏ nó! Hãy thêm Lộ trình API cuối cùng của chúng ta trong app/api/remove.ts . Sao chép mã sau vào tệp:

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;

Nhận thấy bất cứ điều gì tương tự? Đó là bởi vì tuyến đường này gần giống với add Lộ trình API. Sự khác biệt chính ở đây là chúng tôi đang sử dụng LREM , không phải LPUSH , để xóa một mục khỏi Redis.

Xây dựng giao diện người dùng

Để bắt đầu, hãy xóa mọi thứ trong app/pages/index.js và viết danh sách Việc cần làm của chúng tôi theo từng bước.

Ở đầu tệp, hãy dán những lần nhập này vào.

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

Chúng tôi sẽ sử dụng React Hooks để xây dựng chức năng cốt lõi của danh sách Việc cần làm của chúng tôi. Hãy triển khai một số tính năng cốt lõi của danh sách.

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/> thành phần là cốt lõi của ứng dụng của chúng tôi. Xem xét kỹ hơn mã của nó cho chúng ta biết cách chúng ta sử dụng nó. Ở đầu thành phần của chúng tôi, chúng tôi khởi tạo trạng thái cho ứng dụng của mình. Chúng tôi cũng khai báo một ref để sử dụng sau này trong đầu vào "Việc cần làm mới" của chúng tôi. Bạn cũng có thể nhận thấy việc sử dụng antiCSRFToken ! Blitz.js yêu cầu sử dụng các mã thông báo này khi tìm nạp bất kỳ Tuyến API nào để ngăn chặn bất kỳ loại tác nhân độc hại nào làm hại trang web của bạn. Theo tôi, thật tuyệt khi có!

Chúng tôi sử dụng ba chức năng chính để xử lý dữ liệu của chúng tôi trên trang web. Ba điều này là:

  • handleAddTodo
  • handleRemoveTodo
  • fetchTodos

Chúng tôi gọi fetchTodos ngay sau khi tải trang, để tải tất cả Việc cần làm mà người dùng vẫn cần phải hoàn thành. Khi người dùng xóa hoặc thêm Việc cần làm, chúng tôi gọi fetchTodos một lần nữa để phản ánh sự thay đổi đó trên trang web!

Nếu người dùng chưa đăng nhập, người dùng sẽ được nhắc đăng nhập vào trang web trước khi xem trang này.

Bạn có thể đăng ký hoặc đăng nhập nếu bạn chưa có phiên trên trang web. Hãy nhớ rằng, bạn không thể lưu trữ Việc cần làm của mình mà không có tài khoản và tất cả các Tuyến API yêu cầu bạn phải được xác thực bằng AntiCSRFToken !

Nhưng chờ đợi, một bước quan trọng hơn! Chúng ta phải xuất trang!

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;

Như bạn có thể thấy ở trên, Blitz.js sử dụng cách tiếp cận hơi khác so với Tiếp theo, nhưng cách tiếp cận hiện tại vẫn giống nhau về cốt lõi của nó. Chúng tôi sử dụng Suspense mà chúng tôi đã nhập trước đó để cho người dùng biết rằng ứng dụng đang tải và sau đó chúng tôi hiển thị </Main> của chúng tôi thành phần sau khi tải xong!

Để xem các thay đổi của bạn có hiệu lực, hãy chạy phần này trong bảng điều khiển của bạn một lần nữa và điều hướng đến ứng dụng của bạn trong trình duyệt.

blitz dev

Nếu bạn đã làm theo hướng dẫn, ứng dụng của bạn sẽ trông giống như sau khi bạn đăng nhập và thêm một vài Việc cần làm:

Xây dựng danh sách việc cần làm với Blitz.js &Redis

Bạn có thể xóa Công việc cần làm bằng cách nhấp vào hộp bên cạnh, đó là những gì removeTodo hàm dành cho 😉.

Xin chúc mừng!

Tôi hy vọng bạn đã học được điều gì đó mới bằng cách đọc bài đăng trên blog này và nếu bạn chưa đọc, hãy nhớ rằng bạn không cần phải trau dồi kỹ năng của mình! Blitz.js đang chuyển trọng tâm ra khỏi Next.js, vì vậy nó có thể là một khung hoàn toàn khác trong tương lai, nhưng hãy chú ý theo dõi trên trang web của họ tại đây!

Nguồn dự án :Liên kết GitHub

Bản trình diễn làm việc: Liên kết demo

Có phản hồi? Đảm bảo theo dõi @upstash trên Twitter và tham gia máy chủ Discord!