Skip to content

Commit 4172cbc

Browse files
committed
initial commit
0 parents  commit 4172cbc

36 files changed

+8162
-0
lines changed

Diff for: .env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ADMIN_PASSWORD=
2+
DATABASE_URL="postgresql://codersteps:@localhost:5432/fullstack_app?schema=public"

Diff for: .eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

Diff for: .gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env
30+
.env*.local
31+
32+
# vercel
33+
.vercel
34+
35+
# typescript
36+
*.tsbuildinfo

Diff for: .prettierrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"tabWidth": 2,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"semi": false
6+
}

Diff for: README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## About
2+
3+
A Next.js full-stack boilerplate with TypeScript, Tailwind CSS, and Prisma.js for your future full-stack apps.
4+
5+
## Tasks
6+
7+
- [x] Create a new Next.js app
8+
- [x] Install and configure Tailwind CSS
9+
- [x] Set up Typesafe environment variables
10+
- [x] Set up Prisma.js
11+
- [x] Automatize Next.js api handlers and Structure next.js api handlers
12+
- [x] Setup Next.js api hello world middleware
13+
- [x] Set up hello world MOBX store (under review)
14+
15+
## Notes
16+
17+
Don't forget to run: `cp .env.example .env`
18+
19+
Soon the related article will be published at codersteps.com

Diff for: app/users/create.handler.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { NextApiHandler } from 'next'
2+
import bcrypt from 'bcrypt'
3+
import { Prisma, User } from '@prisma/client'
4+
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
5+
import db from '@/lib/database'
6+
7+
interface Data {
8+
error: string | null
9+
data: User | null
10+
}
11+
12+
const createHandler: NextApiHandler<Data> = async (req, res) => {
13+
const { firstName, lastName, bio, username, password } =
14+
req.body as Prisma.UserUncheckedCreateInput
15+
16+
if (
17+
typeof username !== 'string' ||
18+
typeof password !== 'string' ||
19+
password.length < 6
20+
) {
21+
res
22+
.status(StatusCodes.BAD_REQUEST)
23+
.json({ data: null, error: ReasonPhrases.BAD_REQUEST })
24+
return
25+
}
26+
27+
const hashedPassword = bcrypt.hashSync(password, 10)
28+
const data: Prisma.UserUncheckedCreateInput = {
29+
username,
30+
password: hashedPassword,
31+
}
32+
33+
if (typeof firstName === 'string') {
34+
data.firstName = firstName
35+
}
36+
if (typeof lastName === 'string') {
37+
data.lastName = lastName
38+
}
39+
if (typeof bio === 'string') {
40+
data.bio = bio
41+
}
42+
43+
const createdUser = await db.user.create({ data })
44+
res.status(StatusCodes.OK).json({ data: createdUser, error: null })
45+
}
46+
47+
export default createHandler

Diff for: app/users/delete.handler.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { NextApiHandler } from 'next'
2+
import { User } from '@prisma/client'
3+
import { StatusCodes } from 'http-status-codes'
4+
import db from '@/lib/database'
5+
6+
interface Data {
7+
data: User | null
8+
error: string | null
9+
}
10+
11+
const deleteHandler: NextApiHandler<Data> = async (req, res) => {
12+
const id = typeof req.query.id === 'string' ? parseInt(req.query.id, 10) : 0
13+
14+
const user = await db.user.delete({ where: { id } })
15+
res.status(StatusCodes.OK).json({ data: user, error: null })
16+
}
17+
18+
export default deleteHandler

Diff for: app/users/index.handler.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { NextApiHandler } from 'next'
2+
import { User } from '@prisma/client'
3+
import { StatusCodes } from 'http-status-codes'
4+
import db from '@/lib/database'
5+
6+
interface Data {
7+
data: User[]
8+
}
9+
10+
const indexHandler: NextApiHandler<Data> = async (req, res) => {
11+
const users = await db.user.findMany()
12+
res.status(StatusCodes.OK).json({ data: users })
13+
}
14+
15+
export default indexHandler

Diff for: app/users/show.handler.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { NextApiHandler } from 'next'
2+
import { User } from '@prisma/client'
3+
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
4+
import db from '@/lib/database'
5+
6+
interface Data {
7+
data: User | null
8+
error: string | null
9+
}
10+
11+
const showHandler: NextApiHandler<Data> = async (req, res) => {
12+
const id = typeof req.query.id === 'string' ? parseInt(req.query.id, 10) : 0
13+
14+
const user = await db.user.findFirst({ where: { id } })
15+
16+
if (!user) {
17+
res
18+
.status(StatusCodes.NOT_FOUND)
19+
.json({ data: null, error: ReasonPhrases.NOT_FOUND })
20+
}
21+
22+
res.status(StatusCodes.OK).json({ data: user, error: null })
23+
}
24+
25+
export default showHandler

Diff for: app/users/update.handler.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { NextApiHandler } from 'next'
2+
import { Prisma, User } from '@prisma/client'
3+
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
4+
import db from '@/lib/database'
5+
6+
interface Data {
7+
data: User | null
8+
error: string | null
9+
}
10+
11+
const updateHandler: NextApiHandler<Data> = async (req, res) => {
12+
const id = typeof req.query.id === 'string' ? parseInt(req.query.id, 10) : 0
13+
14+
const { firstName, lastName, bio } =
15+
req.body as Prisma.UserUncheckedUpdateInput
16+
17+
const data: Prisma.UserUncheckedUpdateInput = {}
18+
19+
if (typeof firstName === 'string') {
20+
data.firstName = firstName
21+
}
22+
if (typeof lastName === 'string') {
23+
data.lastName = lastName
24+
}
25+
if (typeof bio === 'string') {
26+
data.bio = bio
27+
}
28+
29+
const user = await db.user.update({ data, where: { id } })
30+
res.status(StatusCodes.OK).json({ data: user, error: null })
31+
}
32+
33+
export default updateHandler

Diff for: components/common/Google/Fonts.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default function Fonts() {
2+
return (
3+
<>
4+
<link rel="preconnect" href="https://fonts.googleapis.com" />
5+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
6+
{
7+
// eslint-disable-next-line @next/next/no-page-custom-font
8+
<link
9+
href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,600;0,700;0,800;1,600;1,700;1,800&family=Roboto:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
10+
rel="stylesheet"
11+
></link>
12+
}
13+
</>
14+
);
15+
}

Diff for: components/common/ShowMessage.tsx

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useStore } from '@/store/index'
2+
3+
const ShowMessage = () => {
4+
const { helloWorld } = useStore()
5+
6+
return (
7+
<div className="p-4 border border-gray-500">
8+
Message: {helloWorld.message}
9+
</div>
10+
)
11+
}
12+
13+
export default ShowMessage

Diff for: core/make-handler.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Prisma } from '@prisma/client'
2+
import type { NextApiHandler } from 'next'
3+
import { ReasonPhrases, StatusCodes } from 'http-status-codes'
4+
5+
type Data = {
6+
error: string
7+
}
8+
9+
const makeHandler: (
10+
handlerOptions: {
11+
method: 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE'
12+
handler: NextApiHandler
13+
}[],
14+
) => NextApiHandler<Data> = (handlerOptions) => {
15+
return async (req, res) => {
16+
const handlerOption = handlerOptions.find(
17+
(handlerOption) => handlerOption.method === req.method,
18+
)
19+
20+
if (!handlerOption) {
21+
res.setHeader(
22+
'Allow',
23+
handlerOptions.map((handlerOption) => handlerOption.method),
24+
)
25+
res
26+
.status(StatusCodes.METHOD_NOT_ALLOWED)
27+
.json({ error: ReasonPhrases.METHOD_NOT_ALLOWED })
28+
return
29+
}
30+
31+
try {
32+
await handlerOption.handler(req, res)
33+
} catch (e) {
34+
console.error(e)
35+
36+
if (e instanceof Prisma.PrismaClientKnownRequestError) {
37+
if (e.code === 'P2002') {
38+
res.status(StatusCodes.BAD_REQUEST).json({
39+
error: ReasonPhrases.BAD_REQUEST,
40+
})
41+
return
42+
}
43+
44+
if (e.code === 'P2025') {
45+
res.status(StatusCodes.NOT_FOUND).json({
46+
error: ReasonPhrases.NOT_FOUND,
47+
})
48+
return
49+
}
50+
}
51+
52+
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
53+
error: ReasonPhrases.INTERNAL_SERVER_ERROR,
54+
})
55+
}
56+
}
57+
}
58+
59+
export default makeHandler

Diff for: lib/database.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { PrismaClient } from '@prisma/client'
2+
3+
let prisma: PrismaClient
4+
5+
if (process.env.NODE_ENV === 'production') {
6+
prisma = new PrismaClient()
7+
} else {
8+
if (!global.prisma) {
9+
global.prisma = new PrismaClient()
10+
}
11+
prisma = global.prisma
12+
}
13+
14+
export default prisma

Diff for: middlewares/hello-world.middleware.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { NextApiMiddleware } from '@/types/next'
2+
3+
const helloWorld: NextApiMiddleware = (handler) => {
4+
return async (req, res) => {
5+
if (
6+
typeof req.cookies.sayHello === 'string' &&
7+
req.cookies.sayHello === 'true'
8+
) {
9+
console.log('Hello, World!')
10+
}
11+
12+
await handler(req, res)
13+
}
14+
}
15+
16+
export default helloWorld

Diff for: next-env.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
4+
// NOTE: This file should not be edited
5+
// see https://nextjs.org/docs/basic-features/typescript for more information.

Diff for: next.config.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
}
5+
6+
module.exports = nextConfig

0 commit comments

Comments
 (0)