Build Full-Stack APIs, Connect to Databases, and Handle CRUD Operations
Next.js 14 makes it easier than ever to create and consume backend APIs using the App Router. In this module, you’ll learn to:
- Create REST-style API routes under
app/api/
- Connect Prisma ORM to PostgreSQL or MongoDB
- Build and call API endpoints
- Integrate CRUD logic into Server Actions or Client Components
🧱 Folder Structure
src/
├── app/
│ ├── api/
│ │ └── users/
│ │ └── route.ts ← API endpoint (POST, GET)
│ ├── page.tsx
├── lib/
│ └── prisma.ts ← Prisma client setup
├── server/
│ └── actions/ ← Server Actions
├── components/
✅ Step 1: Set Up Prisma with PostgreSQL or MongoDB
npm install prisma @prisma/client
npx prisma init
🔹 .env
(PostgreSQL example)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
🔹 prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
}
Run the migration:
npx prisma migrate dev --name init
✅ Step 2: Setup Prisma Client
// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma || new PrismaClient({ log: ['query'] });
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
✅ Step 3: Create an API Route – POST /api/users
// src/app/api/users/route.ts
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
const body = await req.json();
const { name, email } = body;
try {
const user = await prisma.user.create({ data: { name, email } });
return NextResponse.json(user, { status: 201 });
} catch (error) {
return NextResponse.json({ error: "User already exists." }, { status: 400 });
}
}
export async function GET() {
const users = await prisma.user.findMany();
return NextResponse.json(users);
}
✅ Step 4: Call API from Client Component (e.g. Create User)
"use client";
import { useState } from "react";
export default function UserForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const res = await fetch("/api/users", {
method: "POST",
body: JSON.stringify({ name, email }),
headers: { "Content-Type": "application/json" },
});
if (res.ok) {
alert("User created!");
} else {
alert("Something went wrong");
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-md">
<input
className="border p-2 w-full"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
required
/>
<input
className="border p-2 w-full"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
type="email"
required
/>
<button className="bg-blue-600 text-white px-4 py-2 rounded" type="submit">
Create User
</button>
</form>
);
}
✅ Step 5: Fetch Users from API
'use client';
import { useEffect, useState } from "react";
type User = { id: number; name: string; email: string };
export default function UserList() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul className="space-y-2 mt-6">
{users.map(user => (
<li key={user.id} className="border p-2 rounded">
{user.name} ({user.email})
</li>
))}
</ul>
);
}
🧠 Optional: Call Prisma Directly from Server Action
// src/server/actions/createUser.ts
'use server';
import { prisma } from "@/lib/prisma";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
return prisma.user.create({ data: { name, email } });
}
Then use in a form:
<form action={createUser}>
<input name="name" />
<input name="email" />
<button type="submit">Submit</button>
</form>
✅ Summary
In this module, you learned how to:
Task | Tool/Example |
---|---|
Setup a database | Prisma + PostgreSQL or MongoDB |
Create backend APIs | app/api/route.ts (RESTful APIs) |
Connect UI to backend | fetch() from Client Component |
Full CRUD logic | Server Actions or API calls |
🔜 Coming Up Next:
In Module 9, we’ll add Authentication & Authorization using next-auth
, implement protected routes, and build a role-based system for admin vs user access.