How to Implement Full Authentication in Next.js with JWT and PostgreSQL

Hi, 👋 I'm Vinay Patel I am a Software Developer with a passion for building scalable and high-performance applications.
In this article, I'll walk you through how I implemented a complete authentication system using:
PostgreSQL for user data
JWT for secure authentication
React Context API for managing auth state
Middleware for protected routes
A dynamic navbar that displays the logged-in user's name
Errors I encountered—and how I fixed them!
Step 1: Setting Up PostgreSQL and Prisma
I started by initializing PostgreSQL and integrating it with Prisma ORM.
Prisma Schema Example:
model User {
id String @id @default(uuid())
name String
email String @unique
password String
createdAt DateTime @default(now())
}
Then ran:
npx prisma migrate dev --name init
This generated the tables in my PostgreSQL database.
Step 2: Creating Auth Utilities with JWT
I created lib/auth.ts to handle:
Password hashing with bcrypt
Token generation with jsonwebtoken
Token verification
Example:
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
export const generateToken = (user: any) => {
return jwt.sign({ id: user.id, email: user.email, name: user.name }, process.env.JWT_SECRET!, {
expiresIn: '1d',
});
};
export const verifyToken = (token: string) => {
return jwt.verify(token, process.env.JWT_SECRET!);
};
Step 3: Backend Login Route
I created /api/auth/login/route.ts to validate credentials and set the token in cookies.
Key steps:
Compare hashed passwords
On success: generate JWT and send as cookie
Return user data
Error Faced:
"Login failed" on every attempt
Solution:
Ensured credentials: 'include' was added in frontend fetch() to allow cookies to persist.
Step 4: Frontend Auth Context
I created a global AuthContext to manage login, logout, register, and current user state using useEffect.
useEffect(() => {
async function loadUser() {
const res = await fetch('/api/auth/me');
if (res.ok) {
const data = await res.json();
setUser(data.user);
}
setLoading(false);
}
loadUser();
}, []);
Step 5: Protecting Routes with Middleware
Used Next.js middleware to check if user has token for protected pages like /cart.
import { NextResponse } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies.get('token')?.value;
if (!token && req.nextUrl.pathname.startsWith('/cart')) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
}
Step 6: Testing Login + Redirect to Cart
After login, user redirected to /cart:
window.location.href = '/cart';
This worked only after fixing credentials: 'include' in the fetch call.
Step 7: Fetch Logged-in User in /api/auth/me
Here’s the important route to get the user from token:
import { cookies } from 'next/headers';
import { verifyToken } from '../../../../lib/auth';
export async function GET() {
const token = cookies().get('token')?.value;
if (!token) return NextResponse.json({ user: null });
try {
const decoded = verifyToken(token);
return NextResponse.json({ user: decoded });
} catch (e) {
return NextResponse.json({ user: null });
}
}
Error Encountered:
Export verifyToken doesn't exist in target module
Fix:
I forgot to export verifyToken from lib/auth.ts. Added:
export const verifyToken = ...
Step 8: Display Logged-in User Name in Navbar
Finally, I updated the Navbar:
const { user, isAuthenticated, loading } = useAuth();
{!loading && isAuthenticated && (
<span className="text-sm font-medium text-gray-700">Hi, {user?.name}</span>
)}
Final Result
PostgreSQL stores secure user data
JWT authenticates users and persists session
Middleware protects sensitive routes
Navbar shows real-time user name
Auth state persists with React Context +
/api/auth/me
Bugs I Solved
| Error | Fix |
verifyToken not exported | Exported it from lib/auth.ts |
| Login not redirecting | Used credentials: 'include' in fetch |
| Navbar not updating | Used useEffect to fetch user on load |
{"user":{}} | Fixed logic in /api/auth/me to return user properly |
Have questions or improvements? Drop them in the comments.




