Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Redis example to use Tailwind. #23131

Merged
merged 3 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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