URL Shortener with Next.js Route Handlers + Prisma
~ Index Introduction Tech Stack & Setup Schema & Database API Endpoint to Shorten URLs Route Handler to Redirect Optional UI with Client-side Form Bonus Tips Conclusion ~ Introduction Ever sent someone a massive link that looks like it came out of a horror movie script? It’s 2025. Links should be short and clean. In this post, we’ll build a fully working URL shortener using the new Next.js App Router, Route Handlers, and Prisma with a SQLite database. The whole thing fits into just a few files. No backend frameworks, no third-party services, just pure Next.js magic. Let’s dive in!!! ~ Tech Stack & Setup We’re keeping things light but modern: Next.js (App Router) TypeScript Prisma (ORM) SQLite (because dev should be fast) Init your project npx create-next-app@latest url-shortener --experimental-app --typescript cd url-shortener Install dependencies npm install prisma @prisma/client npx prisma init Configure .env DATABASE_URL="file:./dev.db" NEXT_PUBLIC_BASE_URL="http://localhost:3000" ~ Schema & Database Open prisma/schema.prisma and drop this in: generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model Link { id Int @id @default(autoincrement()) slug String @unique url String createdAt DateTime @default(now()) } Create the database npx prisma migrate dev --name init That’s your backend. Done. ~ API Endpoint to Shorten URLs File: app/api/shorten/route.ts import { NextRequest, NextResponse } from 'next/server'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); function generateSlug(length = 6) { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); } export async function POST(request: NextRequest) { const { url } = await request.json(); if (!url) return NextResponse.json({ error: 'Missing URL' }, { status: 400 }); let slug = generateSlug(); while (await prisma.link.findUnique({ where: { slug } })) { slug = generateSlug(); } const record = await prisma.link.create({ data: { url, slug } }); const shortUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/${record.slug}`; return NextResponse.json({ slug, shortUrl }); } ~ Route Handler to Redirect File: app/[slug]/route.ts import { NextRequest, NextResponse } from 'next/server'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export async function GET(request: NextRequest, { params }: { params: { slug: string } }) { const record = await prisma.link.findUnique({ where: { slug: params.slug } }); if (!record) return new NextResponse('Not found', { status: 404 }); return NextResponse.redirect(record.url); } Your links are now redirecting!!!!!!!!!!! ~ Optional UI with Client-side Form You can just throw in a quick form on your homepage: 'use client'; import { useState } from 'react'; export default function Home() { const [url, setUrl] = useState(''); const [short, setShort] = useState(''); const handleSubmit = async (e: any) => { e.preventDefault(); const res = await fetch('/api/shorten', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }), }); const data = await res.json(); setShort(data.shortUrl); }; return ( URL Shortener setUrl(e.target.value)} placeholder="https://example.com" className="border p-2 rounded" /> Shorten {short && Short URL: {short}} ); } It then should look like this: ~ Bonus Tips Little things to level up: 1. Custom Slugs Want users to pick their own slugs (like mycoolsite.dev/bio)? Just modify the API to accept an optional slug and check if it’s taken. 2. Analytics Tracking Add a clicks field in the model, then increment it on redirect. You’ll have your own tiny analytics dashboard. 3. Deploy with Vercel + PlanetScale (if scaling) SQLite is fine for local dev, but for prod, go MySQL with PlanetScale or NeonDB for Postgres. ~ Conclusion That’s it! You just built a fully working URL shortener with: App Router Route Handlers Prisma ORM SQLite DB One-click redirect magic Optional UI You are now ready to work for google (homeless shelter) Wanna add auth? Rate limiting? QR codes? Let me know! Got questions or ideas? Drop ‘em below. You know what to do. NOW! ~ Luan

~ Index
- Introduction
- Tech Stack & Setup
- Schema & Database
- API Endpoint to Shorten URLs
- Route Handler to Redirect
- Optional UI with Client-side Form
- Bonus Tips
- Conclusion
~ Introduction
Ever sent someone a massive link that looks like it came out of a horror movie script?
It’s 2025. Links should be short and clean.
In this post, we’ll build a fully working URL shortener using the new Next.js App Router, Route Handlers, and Prisma with a SQLite database. The whole thing fits into just a few files.
No backend frameworks, no third-party services, just pure Next.js magic.
Let’s dive in!!!
~ Tech Stack & Setup
We’re keeping things light but modern:
- Next.js (App Router)
- TypeScript
- Prisma (ORM)
- SQLite (because dev should be fast)
Init your project
npx create-next-app@latest url-shortener --experimental-app --typescript
cd url-shortener
Install dependencies
npm install prisma @prisma/client
npx prisma init
Configure .env
DATABASE_URL="file:./dev.db"
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
~ Schema & Database
Open prisma/schema.prisma
and drop this in:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Link {
id Int @id @default(autoincrement())
slug String @unique
url String
createdAt DateTime @default(now())
}
Create the database
npx prisma migrate dev --name init
That’s your backend. Done.
~ API Endpoint to Shorten URLs
File: app/api/shorten/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
function generateSlug(length = 6) {
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
export async function POST(request: NextRequest) {
const { url } = await request.json();
if (!url) return NextResponse.json({ error: 'Missing URL' }, { status: 400 });
let slug = generateSlug();
while (await prisma.link.findUnique({ where: { slug } })) {
slug = generateSlug();
}
const record = await prisma.link.create({ data: { url, slug } });
const shortUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/${record.slug}`;
return NextResponse.json({ slug, shortUrl });
}
~ Route Handler to Redirect
File: app/[slug]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET(request: NextRequest, { params }: { params: { slug: string } }) {
const record = await prisma.link.findUnique({ where: { slug: params.slug } });
if (!record) return new NextResponse('Not found', { status: 404 });
return NextResponse.redirect(record.url);
}
Your links are now redirecting!!!!!!!!!!!
~ Optional UI with Client-side Form
You can just throw in a quick form on your homepage:
'use client';
import { useState } from 'react';
export default function Home() {
const [url, setUrl] = useState('');
const [short, setShort] = useState('');
const handleSubmit = async (e: any) => {
e.preventDefault();
const res = await fetch('/api/shorten', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url }),
});
const data = await res.json();
setShort(data.shortUrl);
};
return (
<main className="flex flex-col items-center justify-center h-screen p-4">
<h1 className="text-2xl mb-4">URL Shortenerh1>
<form onSubmit={handleSubmit} className="flex space-x-2">
<input
type="url"
required
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com"
className="border p-2 rounded"
/>
<button type="submit" className="bg-blue-500 text-white p-2 rounded">
Shorten
button>
form>
{short && <p className="mt-4">Short URL: <a href={short}>{short}a>p>}
main>
);
}
It then should look like this:
~ Bonus Tips
Little things to level up:
1. Custom Slugs
Want users to pick their own slugs (like mycoolsite.dev/bio
)?
Just modify the API to accept an optional slug
and check if it’s taken.
2. Analytics Tracking
Add a clicks
field in the model, then increment it on redirect.
You’ll have your own tiny analytics dashboard.
3. Deploy with Vercel + PlanetScale (if scaling)
SQLite is fine for local dev, but for prod, go MySQL with PlanetScale or NeonDB for Postgres.
~ Conclusion
That’s it! You just built a fully working URL shortener with:
- App Router Route Handlers
- Prisma ORM
- SQLite DB
- One-click redirect magic
- Optional UI
You are now ready to work for google (homeless shelter)
Wanna add auth? Rate limiting? QR codes? Let me know!
Got questions or ideas?
Drop ‘em below.
You know what to do.
NOW!
~ Luan