Skip to content
This repository has been archived by the owner on Jun 21, 2023. It is now read-only.

Commit

Permalink
Update Redis example to use Tailwind. (vercel#23131)
Browse files Browse the repository at this point in the history
And an improved design 😄 

https://roadmap-redis.vercel.app/
  • Loading branch information
leerob authored Mar 17, 2021
1 parent 7e974cc commit 74eb1ce
Show file tree
Hide file tree
Showing 28 changed files with 379 additions and 443 deletions.
68 changes: 16 additions & 52 deletions examples/with-redis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,31 @@

This example showcases how to use Redis as a data store in a Next.js project.

The example is a roadmap voting application where users can enter and vote for feature requests. It features the
following:
The example is a roadmap voting application where users can enter and vote for feature requests. It features the following:

- Users can add and upvote items (features in the roadmap), and enter their email addresses to be notified about the
released items.
- The API records the ip-addresses of the voters, so it does not allow multiple votes on the same item from the same IP
address.
- Users can add and upvote items (features in the roadmap)
- Users can enter their email addresses to be notified about the released items.
- The API records the ip-addresses of the voters, so it does not allow multiple votes on the same item from the same IP address.

## Demo

See
[https://roadmap.upstash.com](https://roadmap.upstash.com)
- [https://roadmap-redis.vercel.app/](https://roadmap-redis.vercel.app/)

## Deploy Your Own

You can deploy Roadmap Voting App for your project/company
using [Vercel and Upstash](https://vercel.com/integrations/upstash) clicking the below button:
This examples uses [Upstash](https://upstash.com) (Serverless Redis Database) as its data storage. During deployment,
you will be asked to connect with Upstash. The integration will help you create a free Redis database and link it to your Vercel project automatically.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fupstash%2Fserverless-tutorials%2Ftree%2Fmaster%2Froadmap-voting-app&env=LOGO&envDescription=Enter%20URL%20for%20your%20project%2Fcompany%20logo&envLink=https%3A%2F%2Fdocs.upstash.com%2Fdocs%2Ftutorials%2Froadmap_voting_app&project-name=roadmap-voting&repo-name=roadmap-voting&demo-title=Roadmap%20Voting&demo-description=Roadmap%20Voting%20Page%20for%20Your%20Project&demo-url=https%3A%2F%2Froadmap.upstash.com&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fnext.js%2Ftree%2Fcanary%2Fexamples%2Fwith-redis&project-name=redis-roadmap&repository-name=redis-roadmap&demo-title=Redis%20Roadmap&demo-description=Create%20and%20upvote%20features%20for%20your%20product.&demo-url=https%3A%2F%2Froadmap-redis.vercel.app%2F&integration-ids=oac_V3R1GIpkoJorr6fqyiwdhl17)

## Configuration
## How to use

The application uses [Upstash](https://upstash.com) (Serverless Redis Database) as its data storage. During deployment,
you will be asked to integrate Upstash. The integration dialog will help you create an Upstash database for free and
link it to your Vercel project with the following steps:
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:

### Deployment Steps
```bash
npx create-next-app --example with-redis roadmap
# or
yarn create next-app --example with-redis roadmap
```

After clicking the deploy button, enter a name for your project. Then you will be asked to install Upstash integration.
<br/>
<img src="./docs/s2.png" width="300" />
<br/>

You can sign up/sign in the following dialog:

<img src="./docs/s3.png" width="300" />

Create a free database:

<img src="./docs/s4.png" width="300" />

Select your database and the Vercel project:

<img src="./docs/s5.png" width="300" />

Click `COMPLETE ON VERCEL` button:

<img src="./docs/s6.png" width="300" />

Finish you deployment by choosing a repository to host the project. In the next step, set the URL of your project's
logo:

<img src="./docs/s7.png" width="300" />

Your Roadmap Voting Page should be ready:

<img src="./docs/s8.png" width="300" />

### Maintenance

The application uses a Redis database to store the feature requests and emails. The features requests are kept in a
sorted set with name `roadmap`. You can connect to it via Redis-cli and manage the data using the
command `zrange roadmap 0 1000 WITHSCORES`. The emails are stored in a set with name `emails`. So you can get the list
by the command `smembers emails`.
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
Binary file removed examples/with-redis/docs/s1.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s2.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s3.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s4.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s5.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s6.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s7.png
Binary file not shown.
Binary file removed examples/with-redis/docs/s8.png
Binary file not shown.
5 changes: 5 additions & 0 deletions examples/with-redis/lib/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Redis from 'ioredis'

const redis = new Redis(process.env.REDIS_URL)

export default redis
19 changes: 13 additions & 6 deletions examples/with-redis/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
{
"name": "with-redis",
"version": "0.1.1",
"version": "0.1.0",
"license": "MIT",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"ioredis": "^4.22.0",
"autoprefixer": "^10.0.4",
"clsx": "^1.1.1",
"ioredis": "^4.23.0",
"next": "latest",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-toastify": "^6.0.8"
"postcss": "^8.1.10",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-hot-toast": "^1.0.2",
"swr": "^0.5.1",
"tailwindcss": "^2.0.2",
"uuid": "^8.3.2"
},
"devDependencies": {
"prettier": "^2.2.1"
"@tailwindcss/jit": "0.1.1"
}
}
27 changes: 22 additions & 5 deletions examples/with-redis/pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import './index.css'
import 'react-toastify/dist/ReactToastify.css'
import '../styles/globals.css'
import { Toaster } from 'react-hot-toast'

// This default export is required in a new `pages/_app.js` file.
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
function MyApp({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<Toaster
position="bottom-right"
toastOptions={{
style: {
margin: '40px',
background: '#363636',
color: '#fff',
zIndex: 1,
},
duration: 5000,
}}
/>
</>
)
}

export default MyApp
35 changes: 21 additions & 14 deletions examples/with-redis/pages/api/create.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import { getRedis } from './utils'
import { v4 as uuidv4 } from 'uuid'

import redis from '../../lib/redis'

export default async function upvote(req, res) {
const { title } = req.body

module.exports = async (req, res) => {
let redis = getRedis()
const body = req.body
const title = body['title']
if (!title) {
redis.quit()
res.json({
res.status(400).json({
error: 'Feature can not be empty',
})
} else if (title.length < 70) {
await redis.zadd('roadmap', 'NX', 1, title)
redis.quit()
res.json({
} else if (title.length < 150) {
const id = uuidv4()
const newEntry = {
id,
title,
created_at: Date.now(),
score: 1,
ip: 'NA',
}

await redis.hset('features', id, JSON.stringify(newEntry))
res.status(200).json({
body: 'success',
})
} else {
redis.quit()
res.json({
error: 'Max 70 characters please.',
res.status(400).json({
error: 'Max 150 characters please.',
})
}
}
9 changes: 9 additions & 0 deletions examples/with-redis/pages/api/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import redis from '../../lib/redis'

export default async function upvote(req, res) {
const features = (await redis.hvals('features'))
.map((entry) => JSON.parse(entry))
.sort((a, b) => b.score - a.score)

res.status(200).json({ features })
}
19 changes: 0 additions & 19 deletions examples/with-redis/pages/api/list.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import { getRedis } from './utils'
import redis from '../../lib/redis'

module.exports = async (req, res) => {
let redis = getRedis()

const body = req.body
const email = body['email']

redis.on('error', function (err) {
throw err
})
export default async function upvote(req, res) {
const { email } = req.body

if (email && validateEmail(email)) {
await redis.sadd('emails', email)
redis.quit()
res.json({
res.status(201).json({
body: 'success',
})
} else {
redis.quit()
res.json({
res.status(400).json({
error: 'Invalid email',
})
}
Expand Down
18 changes: 0 additions & 18 deletions examples/with-redis/pages/api/utils.js

This file was deleted.

33 changes: 18 additions & 15 deletions examples/with-redis/pages/api/vote.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import { getRedis } from './utils'
import redis from '../../lib/redis'

module.exports = async (req, res) => {
let redis = getRedis()
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({
export default async function upvote(req, res) {
const { title, id } = req.body
const ip =
req.headers['x-forwarded-for'] || req.headers['Remote_Addr'] || 'NA'
const count = ip === 'NA' ? 1 : await redis.sadd('s:' + title, ip)

if (count === 0) {
res.status(400).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,
})
const entry = JSON.parse((await redis.hget('features', id)) || 'null')
const updated = {
...entry,
score: entry.score + 1,
ip,
}

await redis.hset('features', id, JSON.stringify(updated))
return res.status(201).json(updated)
}
}
Loading

0 comments on commit 74eb1ce

Please sign in to comment.