Stop 19 · Build Full-Stack Portfolio ProjectsProjects · 10/12

Admin Dashboard with Auth

Part of the Full Stack Developer Roadmap

In short

Every company has an admin panel nobody shows off but everyone uses: users, orders, refunds, charts. Building one with proper roles proves you can be trusted with real data.

A protected, data-dense admin interface — charts, tables, and role-based views — demonstrating you can build the 'boring but essential' internal tools companies always need.

The purple blob sits at a warden's desk with a ring of keys, a register and a bar chart poster.

Think of it like this

The hostel warden's office

The warden's office has the register, the room keys and the attendance charts. The gate guard checks you're a resident, but only the warden and deputy get inside the office. Two checks, two levels of trust.

Watch it happen

Opening /admin/orders

Proxy
Session
Role check
Query
Render

1/5A fast first gate: no session cookie at all? Redirect to /login before any page code runs.

The big ideas

What you need to know

Roles, not just login

Admin, support and viewer can see different things. Store the role on the user and check it on the server.

Tables do the heavy lifting

Server-side pagination, sorting, filters and search. A table with 2 lakh rows must not load them all.

Audit every action

Log who refunded what and when. Admin power without a trail is a problem waiting to happen.

Charts from aggregates

Let the database do SUM and GROUP BY by day. Send the chart 30 numbers, not 30,000 orders.

See it in code

Walk through the code

tsx
1// proxy.ts (middleware.ts before Next.js 16): a quick first gate for /admin2import { NextResponse, type NextRequest } from "next/server";3export function proxy(req: NextRequest) {4  if (!req.cookies.get("session")) return NextResponse.redirect(new URL("/login", req.url));5}6export const config = { matcher: ["/admin/:path*"] };7 8// app/admin/page.tsx: the real check, right next to the data9import { redirect } from "next/navigation";10export default async function AdminPage() {11  const user = await getCurrentUser(); // verifies the session on the server12  if (user?.role !== "ADMIN") redirect("/403");13  const stats = await db.order.aggregate({ _sum: { totalPaise: true }, _count: true });14  return <StatsCards stats={stats} />;15}

1/3Runs before /admin pages. It only checks a cookie exists, which is fast but not proof of anything.

Two files: a cheap cookie check at the edge, then the real session and role check inside the page that reads the data. Server actions need the same check again.

Save yourself the pain

Common mistakes

Oops

Relying only on middleware or proxy to protect admin pages and APIs.

Do this instead

Check the session and role again in each page, route handler and server action that reads or changes data.

Oops

Loading every row into the browser and paginating in JavaScript.

Do this instead

Paginate, sort and filter in the database, and send one page at a time.

Your mission

First milestone: a locked admin page

~40 min

Quick check

Your admin page checks the role, but the /api/admin/refund route doesn't. What can a normal logged-in user do?

Go deeper

  1. 1Add real charts summarizing key metrics from your database.
  2. 2Implement a data table with sorting, filtering and pagination.
  3. 3Restrict access with proper role-based authorization, not just a hidden route.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge