├── .eslintrc.json ├── next.config.js ├── public ├── favicon.ico └── vercel.svg ├── pages ├── api │ ├── createIndex.js │ ├── cars.js │ └── search.js ├── _app.js └── index.js ├── styles └── globals.css ├── package.json ├── .gitignore ├── README.md ├── lib ├── SearchForm.js ├── CarForm.js └── redis.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fireship-io/redis-nextjs-fulltext-search/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /pages/api/createIndex.js: -------------------------------------------------------------------------------- 1 | import { createIndex } from '../../lib/redis'; 2 | 3 | export default async function handler(req, res) { 4 | await createIndex(); 5 | res.status(200).send('ok'); 6 | } 7 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import 'bootstrap/dist/css/bootstrap.css' 3 | 4 | function MyApp({ Component, pageProps }) { 5 | return 6 | } 7 | 8 | export default MyApp 9 | -------------------------------------------------------------------------------- /pages/api/cars.js: -------------------------------------------------------------------------------- 1 | import { createCar } from '../../lib/redis'; 2 | 3 | export default async function handler(req, res) { 4 | const id = await createCar(req.body); 5 | console.log(id) 6 | res.status(200).json({ id }) 7 | } -------------------------------------------------------------------------------- /pages/api/search.js: -------------------------------------------------------------------------------- 1 | import { searchCars } from '../../lib/redis'; 2 | 3 | export default async function handler(req, res) { 4 | const q = req.query.q; 5 | const cars = await searchCars(q); 6 | res.status(200).json({ cars }); 7 | } 8 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import CarForm from '../lib/CarForm'; 2 | import SearchForm from '../lib/SearchForm'; 3 | 4 | export default function Home(props) { 5 | return ( 6 |
7 |

Create a Car

8 | 9 |
10 |

Find a Car

11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 10vw; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | min-height: 200vh; 8 | } 9 | 10 | 11 | a { 12 | color: inherit; 13 | text-decoration: none; 14 | } 15 | 16 | * { 17 | box-sizing: border-box; 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-demo", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "bootstrap": "^5.1.3", 12 | "next": "12.0.7", 13 | "react": "17.0.2", 14 | "react-dom": "17.0.2", 15 | "redis-om": "^0.1.7" 16 | }, 17 | "devDependencies": { 18 | "eslint": "8.4.1", 19 | "eslint-config-next": "12.0.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Redis FullText Search 2 | 3 | Build a fulltext instant-search feature with Redis & Next.js. 4 | 5 | - Watch the [video](https://youtu.be/DOIWQddRD5M) on YouTube. 6 | - Follow the full [Redis Next.js tutorial](https://fireship.io/lessons/redis-nextjs) on Fireship.io. 7 | 8 | ### Usage 9 | 10 | ``` 11 | git clone 12 | npm install 13 | ``` 14 | 15 | This demo requires that you have a Redis database running in the cloud or locally. Create a file named `.env.local` and export `REDIS_URL` with your connection details. Example: 16 | 17 | ``` 18 | REDIS_URL=redis://default:PASSWORD@HOST:PORT 19 | ``` -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /lib/SearchForm.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export default function CarForm() { 4 | const [hits, setHits] = useState([]); 5 | 6 | const search = async (event) => { 7 | const q = event.target.value; 8 | 9 | if (q.length > 2) { 10 | const params = new URLSearchParams({ q }); 11 | 12 | const res = await fetch('/api/search?' + params); 13 | 14 | const result = await res.json(); 15 | console.log(result); 16 | setHits(result['cars']); 17 | } 18 | }; 19 | 20 | return ( 21 |
22 | 28 | 29 |
    30 | { 31 | hits.map((hit) => ( 32 |
  • 36 | 37 | 38 |
    39 |
    40 | {hit.make} {hit.model} 41 |
    42 | {hit.description} 43 |
    44 |
  • 45 | ))} 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /lib/CarForm.js: -------------------------------------------------------------------------------- 1 | export default function CarForm() { 2 | const handleSubmit = async (event) => { 3 | event.preventDefault(); 4 | 5 | const form = new FormData(event.target); 6 | const formData = Object.fromEntries(form.entries()); 7 | 8 | console.log(formData); 9 | 10 | const res = await fetch('/api/cars', { 11 | body: JSON.stringify(formData), 12 | headers: { 13 | 'Content-Type': 'application/json', 14 | }, 15 | method: 'POST', 16 | }); 17 | 18 | const result = await res.json(); 19 | console.log(result); 20 | }; 21 | 22 | return ( 23 |
24 | 27 | 28 | 29 | 32 | 33 | 34 | 37 | 38 | 39 | 42 |