서버리스 Redis를 사용한 로드맵 투표 앱
본문
이 튜토리얼에서는 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/
- 이전글무료 17 시간 과정에서 데이터 시각화 배우기 21.02.28
- 다음글React Hooks 구성 요소의 새 수명주기를 설명하는 순서도 21.02.22