This is a dashboard starter template for the NextJS 14 app router based on Auth.js v5.
- Next.js 14
- Auth.js v5 + Prisma Adapter
- Tailwindcss
- Shadcn
- Prisma
- Zustand
- React Query
The folder and file structure is based on nextjs app router next.js project structure.
.
├── actions/ # Server Actions
├── app/ # App Router
│ └── api/
│ ├── auth/ # Authentication
│ └── v1/ # Public APIs
├── components/ # React components
├── config/ # Configuration for site
├── context/ # Context
├── docs/ # Documents
├── hooks/ # Hooks
├── lib/ # Utility functions
├── prisma/ # Prisma Schema Location and Configuration
├── public/ # Static assets to be served
│ └── [locales]/ # Internationalization
├── queries/ # API
├── schemas/ # Schema validations
├── screenshots/ # Screenshots
├── store/ # State
├── types/ # Type definitions
└── package.json
Clone the repository to the current directory.
git clone https://github.com/w3labkr/nextjs14-authjs5-dashboard.git .
Install all modules listed as dependencies.
npm install
Copy of the .env.example
if the .env
doesn't exist.
cp .env.example .env
Create an SQL migration file and execute it.
npx prisma migrate dev --name init
Start the development server.
npm run dev
success
import { ApiResponse } from '@/lib/http'
export async function POST(req) {
return ApiResponse.json({ user: null })
}
// output
// { status: 'success', message: 'OK', success: true, data: { user: null } }
fail
import { ApiResponse } from '@/lib/http'
export async function POST(req) {
return ApiResponse.json({ user: null }, { status: 400 })
}
// output
// { status: 'fail', message: 'Bad Request', success: false, data: { user: null } }
Change URL address to absolute address and return as json response.
import { xhr } from '@/lib/http'
const res = await xhr.get('/api', init)
const res = await xhr.head('/api', init)
const res = await xhr.post('/api', init)
const res = await xhr.put('/api', init)
const res = await xhr.delete('/api', init)
const res = await xhr.patch('/api', init)
import {
STATUS_CODES,
STATUS_TEXTS,
STATUS_CODE_TO_TEXT,
STATUS_TEXT_TO_CODE
} from '@/lib/http-status-codes/en'
STATUS_CODES.OK // 200
STATUS_TEXTS.OK // "OK"
STATUS_CODE_TO_TEXT["200"] // "OK"
STATUS_TEXT_TO_CODE["OK"] // "200"
import { generateHash, compareHash } from '@/lib/bcrypt'
const hashed = await generateHash('hash')
if (await compareHash('hash', hashed)) {
// isMatch
}
import {
decodeJwt,
verifyJwt,
jwtSign,
generateRecoveryToken,
generateAccessToken,
generateRefreshToken,
generateTokenExpiresAt,
isTokenExpired
} from '@/lib/jose'
decodeJwt(jwt: string)
verifyJwt(jwt: string | Uint8Array, options?: JWTVerifyOptions)
jwtSign(sub: string, exp: number | string | Date = '1h', payload?: JWTPayload)
generateRecoveryToken(sub: string, payload?: JWTPayload)
generateAccessToken(sub: string)
generateRefreshToken(sub: string, jwt?: string | null)
generateTokenExpiresAt(expiresIn: number = 60 * 60)
isTokenExpired(
expiresAt: number,
options?: { expiresIn?: number; expiresBefore?: number }
)
Route Handlers
import { verifyCSRFToken } from '@/lib/csrf'
export async function POST(req) {
const { csrfToken, ...body } = await req.json()
if (!verifyCSRFToken(csrfToken)) {
return new Response('Unauthorized', { status: 401 })
}
}
Client Side
'use client'
import { useCSRFToken } from '@/hooks/use-csrf-token'
export function Component() {
const { csrfToken } = useCSRFToken()
async function onSubmit() {
const res = await fetch('/api', {
method: 'POST',
body: JSON.stringify({ csrfToken }),
})
}
return <button onClick={onSubmit}>Submit</button>
}
Sendmail
import { transporter, sender } from '@/lib/nodemailer'
try {
const info = await transporter.sendMail({
from: `"${sender?.name}" <${sender?.email}>`,
to: '[email protected]',
subject: `[${sender?.name}] Reset your password`,
text: 'Hello World!',
html: '<h2>Hello World!<h2>',
})
} catch (e) {
console.log(e)
}
The time zone and localized format are set.
import dayjs from '@/lib/dayjs'
dayjs().toISOString()
import { LucideIcon } from '@/lib/lucide-icon'
<LucideIcon name="Heart"/>
import {
getRandom,
getRandomArbitrary,
getRandomInt,
getRandomIntInclusive
} from '@/lib/math'
getRandom()
getRandomArbitrary(min: number, max: number)
getRandomInt(min: number, max: number)
getRandomIntInclusive(min: number, max: number)
import {
cn,
uuidv4,
sleep,
fetcher,
absoluteUrl,
relativeUrl
} from '@/lib/utils'
cn(...inputs: ClassValue[])
uuidv4()
sleep(ms: number)
fetcher(input: RequestInfo | URL, init?: RequestInit)
absoluteUrl(url: string | URL, base?: string | URL)
relativeUrl(url: string | URL, base?: string | URL)
This software license under the MIT License.