-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 291ba1e
Showing
19 changed files
with
844 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env.local | ||
.env.development.local | ||
.env.test.local | ||
.env.production.local | ||
|
||
# vercel | ||
.vercel |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: yarn start |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
--- | ||
title: NextJS Prisma | ||
description: A NextJS app using Prisma with a PostgreSQL database | ||
tags: | ||
- next | ||
- prisma | ||
- postgresql | ||
- typescript | ||
--- | ||
|
||
# NextJS Prisma Example | ||
|
||
This example is a [NextJS](https://nextjs.org/) todo app that uses | ||
[Prisma](https://www.prisma.io/) to store todos in Postgres. | ||
|
||
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new?template=https%3A%2F%2Fgithub.com%2Frailwayapp%2Fexamples%2Ftree%2Fmaster%2Fexamples%2Fnextjs-prisma&plugins=postgresql) | ||
|
||
## ✨ Features | ||
|
||
- Prisma | ||
- NextJS | ||
- Postgres | ||
- TypeScript | ||
|
||
## 💁♀️ How to use | ||
|
||
- [Provision a Postgres container on Railway](https://dev.new) | ||
- Connect to your Railway project with `railway link` | ||
- Migrate the database `railway run yarn migrate:dev` | ||
- Run the NextJS app `railway run yarn dev` | ||
|
||
## 📝 Notes | ||
|
||
This app is a simple todo list where the data is persisted to Postgres. [Prisma | ||
migrations](https://www.prisma.io/docs/concepts/components/prisma-migrate#prisma-migrate) | ||
can be created with `railway run yarn migrate:dev` and deployed with `railway run yarn migrate:deploy`. The Prisma client can be regenerated with | ||
`yarn generate`. | ||
|
||
[swr](https://swr.vercel.app/) is used to fetch data on the client and perform optimistic updates. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/// <reference types="next" /> | ||
/// <reference types="next/types/global" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "with-nextjs-postgres", | ||
"version": "0.1.0", | ||
"private": true, | ||
"scripts": { | ||
"dev": "next dev", | ||
"build": "yarn migrate:deploy && next build", | ||
"start": "next start --port ${PORT-3000}", | ||
"migrate:dev": "prisma migrate dev --preview-feature", | ||
"migrate:deploy": "prisma migrate deploy --preview-feature", | ||
"migrate:status": "prisma migrate status --preview-feature", | ||
"generate": "prisma generate" | ||
}, | ||
"dependencies": { | ||
"@prisma/client": "2.30.0", | ||
"next": "12.1.0", | ||
"pg": "^8.5.1", | ||
"react": "17.0.1", | ||
"react-dom": "17.0.1", | ||
"swr": "^0.4.1" | ||
}, | ||
"devDependencies": { | ||
"prisma": "2.30.0", | ||
"@types/node": "^14.14.22", | ||
"@types/react": "^17.0.0", | ||
"typescript": "^4.1.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
-- CreateTable | ||
CREATE TABLE "Todo" ( | ||
"id" TEXT NOT NULL, | ||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"text" TEXT NOT NULL, | ||
"completed" BOOLEAN NOT NULL, | ||
|
||
PRIMARY KEY ("id") | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Please do not edit this file manually | ||
provider = "postgresql" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// This is your Prisma schema file, | ||
// learn more about it in the docs: https://pris.ly/d/prisma-schema | ||
|
||
datasource db { | ||
provider = "postgresql" | ||
url = env("DATABASE_URL") | ||
} | ||
|
||
generator client { | ||
provider = "prisma-client-js" | ||
} | ||
|
||
model Todo { | ||
id String @id @default(uuid()) | ||
createdAt DateTime @default(now()) | ||
text String | ||
completed Boolean | ||
} |
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import useSWR, { mutate } from "swr"; | ||
import { Todo } from "./types"; | ||
|
||
const todoPath = "/api/todos"; | ||
|
||
export const useTodos = () => useSWR<Todo[]>(todoPath); | ||
|
||
export const createTodo = async (text: string) => { | ||
mutate( | ||
todoPath, | ||
todos => [{ text, completed: false, id: "new-todo" }, ...todos], | ||
false, | ||
); | ||
await fetch(todoPath, { | ||
method: "POST", | ||
body: JSON.stringify({ text }), | ||
}); | ||
|
||
mutate(todoPath); | ||
}; | ||
|
||
export const toggleTodo = async (todo: Todo) => { | ||
mutate( | ||
todoPath, | ||
todos => | ||
todos.map(t => | ||
t.id === todo.id ? { ...todo, completed: !t.completed } : t, | ||
), | ||
false, | ||
); | ||
await fetch(`${todoPath}?todoId=${todo.id}`, { | ||
method: "PUT", | ||
body: JSON.stringify({ completed: !todo.completed }), | ||
}); | ||
mutate(todoPath); | ||
}; | ||
|
||
export const deleteTodo = async (id: string) => { | ||
mutate(todoPath, todos => todos.filter(t => t.id !== id), false); | ||
await fetch(`${todoPath}?todoId=${id}`, { method: "DELETE" }); | ||
mutate(todoPath); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import '../styles/globals.css' | ||
|
||
function MyApp({ Component, pageProps }) { | ||
return <Component {...pageProps} /> | ||
} | ||
|
||
export default MyApp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { NextApiRequest, NextApiResponse } from "next"; | ||
import { PrismaClient } from "@prisma/client"; | ||
|
||
const prisma = new PrismaClient(); | ||
|
||
export default async (req: NextApiRequest, res: NextApiResponse) => { | ||
if (req.method === "GET") { | ||
// get all todos | ||
const todos = await prisma.todo.findMany({ | ||
orderBy: { createdAt: "desc" }, | ||
}); | ||
res.json(todos); | ||
} else if (req.method === "POST") { | ||
// create todo | ||
const text = JSON.parse(req.body).text; | ||
const todo = await prisma.todo.create({ | ||
data: { text, completed: false }, | ||
}); | ||
|
||
res.json(todo); | ||
} else if (req.method === "PUT") { | ||
// update todo | ||
const id = req.query.todoId as string; | ||
const data = JSON.parse(req.body); | ||
const todo = await prisma.todo.update({ | ||
where: { id }, | ||
data, | ||
}); | ||
|
||
res.json(todo); | ||
} else if (req.method === "DELETE") { | ||
// delete todo | ||
const id = req.query.todoId as string; | ||
await prisma.todo.delete({ where: { id } }); | ||
|
||
res.json({ status: "ok" }); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { NextPage } from "next"; | ||
import Head from "next/head"; | ||
import { useMemo, useState } from "react"; | ||
import { createTodo, deleteTodo, toggleTodo, useTodos } from "../api"; | ||
import styles from "../styles/Home.module.css"; | ||
import { Todo } from "../types"; | ||
|
||
export const TodoList: React.FC = () => { | ||
const { data: todos, error } = useTodos(); | ||
|
||
if (error != null) return <div>Error loading todos...</div>; | ||
if (todos == null) return <div>Loading...</div>; | ||
|
||
if (todos.length === 0) { | ||
return <div className={styles.emptyState}>Try adding a todo ☝️️</div>; | ||
} | ||
|
||
return ( | ||
<ul className={styles.todoList}> | ||
{todos.map(todo => ( | ||
<TodoItem todo={todo} /> | ||
))} | ||
</ul> | ||
); | ||
}; | ||
|
||
const TodoItem: React.FC<{ todo: Todo }> = ({ todo }) => ( | ||
<li className={styles.todo}> | ||
<label | ||
className={`${styles.label} ${todo.completed ? styles.checked : ""}`} | ||
> | ||
<input | ||
type="checkbox" | ||
checked={todo.completed} | ||
className={`${styles.checkbox}`} | ||
onChange={() => toggleTodo(todo)} | ||
/> | ||
{todo.text} | ||
</label> | ||
|
||
<button className={styles.deleteButton} onClick={() => deleteTodo(todo.id)}> | ||
✕ | ||
</button> | ||
</li> | ||
); | ||
|
||
const AddTodoInput = () => { | ||
const [text, setText] = useState(""); | ||
|
||
return ( | ||
<form | ||
onSubmit={async e => { | ||
e.preventDefault(); | ||
createTodo(text); | ||
setText(""); | ||
}} | ||
className={styles.addTodo} | ||
> | ||
<input | ||
className={styles.input} | ||
placeholder="Buy some milk" | ||
value={text} | ||
onChange={e => setText(e.target.value)} | ||
/> | ||
<button className={styles.addButton}>Add</button> | ||
</form> | ||
); | ||
}; | ||
|
||
const Home: NextPage = () => { | ||
return ( | ||
<div className={styles.container}> | ||
<Head> | ||
<title>Railway NextJS Prisma</title> | ||
<link rel="icon" href="/favicon.ico" /> | ||
</Head> | ||
|
||
<header className={styles.header}> | ||
<h1 className={styles.title}>Todos</h1> | ||
<h2 className={styles.desc}> | ||
NextJS app connected to Postgres using Prisma and hosted on{" "} | ||
<a href="https://railway.app">Railway</a> | ||
</h2> | ||
</header> | ||
|
||
<main className={styles.main}> | ||
<AddTodoInput /> | ||
|
||
<TodoList /> | ||
</main> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Home; |
Oops, something went wrong.