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

Enhance/rest nextjs api routes #4975

Open
wants to merge 7 commits into
base: latest
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions typescript/rest-nextjs-api-routes/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# lock files
*lock.*
31 changes: 14 additions & 17 deletions typescript/rest-nextjs-api-routes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ npx prisma migrate dev --name init

When `npx prisma migrate dev` is executed against a newly created database, seeding is also triggered. The seed file in [`prisma/seed.ts`](./prisma/seed.ts) will be executed and your database will be populated with the sample data.


### 3. Start the app

```
Expand All @@ -63,7 +62,7 @@ The app is now running, navigate to [`http://localhost:3000/`](http://localhost:

![](https://imgur.com/eepbOUO.png)

**Signup** (located in [`./pages/signup.tsx`](./pages/signup.tsx))
**Add Author** (located in [`./pages/add-author.tsx`](./pages/add-author.tsx))

![](https://imgur.com/iE6OaBI.png)

Expand Down Expand Up @@ -137,8 +136,8 @@ model Post {
}

model User {
id Int @default(autoincrement()) @id
name String?
id Int @default(autoincrement()) @id
name String?
email String @unique
posts Post[]
+ profile Profile?
Expand Down Expand Up @@ -167,56 +166,54 @@ You can now use your `PrismaClient` instance to perform operations against the n
```ts
const profile = await prisma.profile.create({
data: {
bio: "Hello World",
bio: 'Hello World',
user: {
connect: { email: "[email protected]" },
connect: { email: '[email protected]' },
},
},
});
})
```

#### Create a new user with a new profile

```ts
const user = await prisma.user.create({
data: {
email: "[email protected]",
name: "John",
email: '[email protected]',
name: 'John',
profile: {
create: {
bio: "Hello World",
bio: 'Hello World',
},
},
},
});
})
```

#### Update the profile of an existing user

```ts
const userWithUpdatedProfile = await prisma.user.update({
where: { email: "[email protected]" },
where: { email: '[email protected]' },
data: {
profile: {
update: {
bio: "Hello Friends",
bio: 'Hello Friends',
},
},
},
});
})
```


### 3. Build new UI features in React

Once you have added a new endpoint to the API (e.g. `/api/profile` with `/POST`, `/PUT` and `GET` operations), you can start building a new UI component in React. It could e.g. be called `profile.tsx` and would be located in the `pages` directory.

In the application code, you can access the new endpoint via `fetch` operations and populate the UI with the data you receive from the API calls.


## Switch to another database (e.g. PostgreSQL, MySQL, SQL Server, MongoDB)

If you want to try this example with another database than SQLite, you can adjust the the database connection in [`prisma/schema.prisma`](./prisma/schema.prisma) by reconfiguring the `datasource` block.
If you want to try this example with another database than SQLite, you can adjust the the database connection in [`prisma/schema.prisma`](./prisma/schema.prisma) by reconfiguring the `datasource` block.

Learn more about the different connection configurations in the [docs](https://www.prisma.io/docs/reference/database-reference/connection-urls).

Expand Down
22 changes: 9 additions & 13 deletions typescript/rest-nextjs-api-routes/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,29 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import styles from '@/components/Header.module.css'

const Header: React.FC = () => {
export default function Header() {
const router = useRouter()
const isActive: (pathname: string) => boolean = (pathname) =>
router.pathname === pathname

return (
<nav>
<div className={styles.left}>
<Link href="/" legacyBehavior>
<a className={styles.bold} data-active={isActive('/')}>
Blog
</a>
<Link href="/" className={styles.bold} data-active={isActive('/')}>
Blog
</Link>
<Link href="/drafts" legacyBehavior>
<a data-active={isActive('/drafts')}>Drafts</a>
<Link href="/drafts" data-active={isActive('/drafts')}>
Drafts
</Link>
</div>
<div className={styles.right}>
<Link href="/signup" legacyBehavior>
<a data-active={isActive('/signup')}>Signup</a>
<Link href="/add-author" data-active={isActive('/add-author')}>
Add Author
</Link>
<Link href="/create" legacyBehavior>
<a data-active={isActive('/create')}>+ Create draft</a>
<Link href="/create" data-active={isActive('/create')}>
+ Create draft
</Link>
</div>
</nav>
)
}

export default Header
18 changes: 7 additions & 11 deletions typescript/rest-nextjs-api-routes/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ import React, { ReactNode } from 'react'
import Header from './Header'
import styles from '@/components/Layout.module.css'

type Props = {
children: ReactNode
export default function Layout({ children }: { children: ReactNode }) {
return (
<div>
<Header />
<div className={styles.layout}>{children}</div>
</div>
)
}

const Layout: React.FC<Props> = (props) => (
<div>
<Header />
<div className={styles.layout}>{props.children}</div>
</div>
)

export default Layout
4 changes: 1 addition & 3 deletions typescript/rest-nextjs-api-routes/src/components/Post.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type PostProps = {
published: boolean
}

const Post: React.FC<{ post: PostProps }> = ({ post }) => {
export default function Post({ post }: { post: PostProps }) {
const authorName = post.author ? post.author.name : 'Unknown author'
return (
<div
Expand All @@ -26,5 +26,3 @@ const Post: React.FC<{ post: PostProps }> = ({ post }) => {
</div>
)
}

export default Post
7 changes: 6 additions & 1 deletion typescript/rest-nextjs-api-routes/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Layout from '@/components/Layout'
import '@/styles/globals.css'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
48 changes: 48 additions & 0 deletions typescript/rest-nextjs-api-routes/src/pages/add-author.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState } from 'react'
import Router from 'next/router'
import styles from '@/styles/AddAuthor.module.css'

export default function AddAuthor() {
const [name, setName] = useState('')
const [email, setEmail] = useState('')

const submitData = async (e: React.SyntheticEvent) => {
e.preventDefault()
try {
const body = { name, email }
await fetch(`/api/user`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
await Router.push('/')
} catch (error) {
console.error(error)
}
}

return (
<div className={styles.page}>
<form onSubmit={submitData}>
<h1>Add Author</h1>
<input
autoFocus
onChange={(e) => setName(e.target.value)}
placeholder="Name"
type="text"
value={name}
/>
<input
onChange={(e) => setEmail(e.target.value)}
placeholder="Email address"
type="text"
value={email}
/>
<input disabled={!name || !email} type="submit" value="Add Author" />
<a className={styles.black} href="#" onClick={() => Router.push('/')}>
or Cancel
</a>
</form>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../lib/prisma'
import prisma from '@/lib/prisma'

// GET /api/filterPosts?searchString=:searchString
export default async function handle(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
import prisma from '@/lib/prisma'

export default async function handle(
req: NextApiRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
import prisma from '@/lib/prisma'

// POST /api/post
// Required fields in body: title, authorEmail
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
import prisma from '@/lib/prisma'

// PUT /api/publish/:id
export default async function handle(
Expand Down
2 changes: 1 addition & 1 deletion typescript/rest-nextjs-api-routes/src/pages/api/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../lib/prisma'
import prisma from '@/lib/prisma'

// POST /api/user
// Required fields in body: name, email
Expand Down
89 changes: 52 additions & 37 deletions typescript/rest-nextjs-api-routes/src/pages/create.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useState } from 'react'
import Layout from '../components/Layout'
import Router from 'next/router'
import styles from '@/styles/Draft.module.css'
import { GetServerSideProps } from 'next'
import prisma from '@/lib/prisma'

const Draft: React.FC = () => {
type Props = {
users: { email: string }[]
}

export default function CreateDraft(props: Props) {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [authorEmail, setAuthorEmail] = useState('')
Expand All @@ -24,42 +29,52 @@ const Draft: React.FC = () => {
}

return (
<Layout>
<div>
<form onSubmit={submitData}>
<h1>Create Draft</h1>
<input
autoFocus
onChange={(e) => setTitle(e.target.value)}
placeholder="Title"
type="text"
value={title}
/>
<input
onChange={(e) => setAuthorEmail(e.target.value)}
placeholder="Author (email address)"
type="text"
<div>
<form onSubmit={submitData}>
<h1>Create Draft</h1>
<input
autoFocus
onChange={(e) => setTitle(e.target.value)}
placeholder="Title"
type="text"
value={title}
/>
<label>
Author Email:{' '}
<select
value={authorEmail}
/>
<textarea
cols={50}
onChange={(e) => setContent(e.target.value)}
placeholder="Content"
rows={8}
value={content}
/>
<input
disabled={!content || !title || !authorEmail}
type="submit"
value="Create"
/>
<a className={styles.black} href="#" onClick={() => Router.push('/')}>
or Cancel
</a>
</form>
</div>
</Layout>
onChange={(e) => setAuthorEmail(e.target.value)}
>
{props.users.map(({ email }, i) => (
<option value={email} key={email + i}>
{email}
</option>
))}
</select>
</label>
<textarea
cols={50}
onChange={(e) => setContent(e.target.value)}
placeholder="Content"
rows={8}
value={content}
/>
<input
disabled={!content || !title || !authorEmail}
type="submit"
value="Create"
/>
<a className={styles.black} href="#" onClick={() => Router.push('/')}>
or Cancel
</a>
</form>
</div>
)
}

export default Draft
export const getServerSideProps: GetServerSideProps = async () => {
const users = await prisma.user.findMany()
return {
props: { users },
}
}
Loading