분류 Reactjs

서버리스 Redis를 사용한 로드맵 투표 앱

컨텐츠 정보

  • 조회 346 (작성일 )

본문

Image for post 


이 튜토리얼에서는 Redis를 Next.js 애플리케이션의 상태 저장소로 사용하는 단일 페이지 애플리케이션을 작성합니다.


이 예는 사용자가 기능 요청을 입력하고 투표하는 기본 로드맵 투표 애플리케이션입니다. Upstash 로드맵 페이지에서 전체 신청서를 확인할 수 있습니다.


Deploy Yourself 


여기에서 전체 애플리케이션의 소스 코드를 확인할 수 있습니다.


Upstash & Vercel 통합 덕분에 Deploy with Vercel을 클릭하여 비용과 코드없이 직접 애플리케이션을 배포 할 수 있습니다.


Next.js 프로젝트 생성 


Next.js를 웹 프레임 워크로 사용할 것입니다. 이제 next.js 앱을 만들고 redis 클라이언트를 먼저 설치해 보도록 하겠습니다.


npx create-next-app nextjs-with-redis


npm install ioredis


index.js 


우리의 응용 프로그램은 단일 페이지가 될 것입니다. 투표 순서와 함께 기능을 나열합니다. 페이지 사용자는 다음과 같은 3 가지 작업을 수행 할 수 있습니다.

  • 사용자가 새로운 기능을 제안합니다.
  • 사용자는 기존 기능에 투표합니다.
  • 사용자는 기능 릴리스에 대한 알림을 받기 위해 이메일을 입력합니다.

아래는 모든 것을 처리하는 부분입니다. 전체 페이지를 확인하려면 여기를 참조하십시오.


import Head from 'next/head'
import { ToastContainer, toast } from 'react-toastify';
import * as React from "react";

class Home extends React.Component {
    ...
    refreshData() {
        fetch("api/list")
            .then(res => res.json())
            .then(
                (result) => {
                    this.setState({
                        isLoaded: true,
                        items: result.body
                    });
                    this.inputNewFeature.current.value = "";
                },
                (error) => {
                    this.setState({
                        isLoaded: true,
                        error
                    });
                }
            )
    }

    vote(event, title) {
        const requestOptions = {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({"title": title})
        };
        console.log(requestOptions);
        fetch('api/vote', requestOptions)
            .then(response => response.json()).then(data => {
                console.log(data)
                if(data.error) {
                    toast.error(data.error, {hideProgressBar: true, autoClose: 3000});
                } else {
                    this.refreshData()
                }
        })
    }

    handleNewFeature(event) {
        const requestOptions = {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({"title": this.inputNewFeature.current.value})
        };
        fetch('api/create', requestOptions)
            .then(response => response.json()).then(data => {
            if(data.error) {
                toast.error(data.error, {hideProgressBar: true, autoClose: 5000});
            } else {
                toast.info("Your feature has been added to the list.", {hideProgressBar: true, autoClose: 3000});
                this.refreshData()
            }
        });
        event.preventDefault();
    }

    handleNewEmail(event) {
        const requestOptions = {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({"email": this.inputEmail.current.value})
        };
        console.log(requestOptions);
        fetch('api/addemail', requestOptions)
            .then(response => response.json()).then(data => {
            if(data.error) {
                toast.error(data.error, {hideProgressBar: true, autoClose: 3000});
            } else {
                toast.info("Your email has been added to the list.", {hideProgressBar: true, autoClose: 3000});
                this.refreshData()
            }
        });
        event.preventDefault();
    }
}
export default Home;


APIs 


Next.js를 사용하면 프로젝트 내에서 서버 측 API를 작성할 수 있습니다. 우리는 4 개의 API를 갖게 될 것입니다.


  • 기능 목록 
  • 기능에 투표하다
  • 새로운 기능 추가
  • 이메일 추가

이제 이러한 API 구현을 살펴 보겠습니다.


list.js 


목록 API는 Redis에 연결하고 Sorted Set 로드맵에서 점수 (투표)별로 정렬 된 기능 요청을 가져옵니다.


import {fixUrl} from "./utils";
import Redis from 'ioredis'

module.exports = async (req, res) => {
    let redis = new Redis(fixUrl(process.env.REDIS_URL));
    let n = await redis.zrevrange("roadmap", 0, 100, "WITHSCORES");
    let result = []
    for (let i = 0; i < n.length - 1; i += 2) {
        let item = {}
        item["title"] = n[i]
        item["score"] = n[i + 1]
        result.push(item)
    }

    redis.quit();

    res.json({
        body: result
    })
}


create.js 


이 API는 Redis 서버에 연결하고 정렬 된 집합 (로드맵)에 새 요소를 추가합니다. ZADD와 함께 "NX"플래그를 사용하므로 사용자는 동일한 제목으로 기존 기능 요청을 덮어 쓸 수 없습니다.


import Redis from 'ioredis'
import {fixUrl} from "./utils";

module.exports = async (req, res) => {
    let redis = new Redis(fixUrl(process.env.REDIS_URL));
    const body = req.body;
    const title = body["title"];
    if (!title) {
        redis.quit()
        res.json({
            error: "Feature can not be empty"
        })
    } else if (title.length < 70) {
        await redis.zadd("roadmap", "NX", 1, title);
        redis.quit()
        res.json({
            body: "success"
        })
    } else {
        redis.quit()
        res.json({
            error: "Max 70 characters please."
        })
    }
}


vote.js 


이 API는 선택한 기능 요청의 점수를 업데이트 (증가)합니다. 또한 동일한 기능 요청에 대한 다중 투표를 방지하기 위해 사용자의 IP 주소를 유지합니다.


import Redis from 'ioredis'
import {fixUrl} from "./utils";

module.exports = async(req, res) => {
    let redis = new Redis(fixUrl(process.env.REDIS_URL));

    const body = req.body
    const title = body["title"];
    let ip = req.headers["x-forwarded-for"] || req.headers["Remote_Addr"] || "NA";
    let c = ip === "NA" ? 1 : await redis.sadd("s:" + title, ip);
    if(c === 0) {
        redis.quit();
        res.json({
            error: "You can not vote an item multiple times"
        })
    } else {
        let v = await redis.zincrby("roadmap", 1, title);
        redis.quit();
        res.json({
            body: v
        })
    }
}


addemail.js 


이 API는 단순히 사용자의 이메일을 Redis Set에 추가합니다. 세트가 이미 고유성을 보장하므로 입력이 유효한 이메일인지 확인하기 만하면 됩니다.


import Redis from 'ioredis'
import {fixUrl} from "./utils";

module.exports = async(req, res) => {
    let redis = new Redis(fixUrl(process.env.REDIS_URL));

    const body = req.body;
    const email = body["email"];

    redis.on("error", function(err) {
        throw err;
    });

    if (email && validateEmail(email) ) {
        await redis.sadd("emails", email );
        redis.quit()
        res.json({
            body: "success"
        })
    } else {
        redis.quit()
        res.json({
            error: "Invalid email"
        })
    }
}

function validateEmail(email) {
    const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(String(email).toLowerCase());
}


CSS 및 유틸리티 


index.css는 페이지가 보기 좋게 보이도록 돕고 utils.js는 Redis URL에서 일반적인 실수를 수정합니다.



노트: 


  • 이 애플리케이션을 Vercel과 함께 배포하는 경우; Vercel은 AWS Lambda 함수를 실행하여 API 구현을 지원합니다. 최상의 성능을 위해 Vercel 기능과 Upstash 클러스터 모두에 대해 동일한 지역을 선택하십시오.
  • Upstash 콘솔을 통해 데이터베이스 세부 정보에 액세스 할 수 있습니다.


https://medium.com/upstash/roadmap-voting-app-with-serverless-redis-9fb1bdc450cf 


클론하여 반영해 본 작업 : https://president-voting.vercel.app/