Shipping small features fast is about having a repeatable path from data to pixels. Here’s a minimal, battle‑tested setup I use to go from an Express API to a typed Next.js UI.
1) Build a tiny Express endpoint
// api/index.ts
import express from "express";
const app = express();
type Todo = { id: number; title: string; done: boolean };
const todos: Todo[] = [
{ id: 1, title: "Wire data model", done: true },
{ id: 2, title: "Render UI", done: false },
];
app.get("/api/todos", (_req, res) => {
res.json(todos);
});
app.listen(3001, () => console.log("API on http://localhost:3001"));
Notes:
- Keep types close to data.
- Return JSON only; let the client shape it for UI.
2) Fetch it in Next.js with types and caching
// src/lib/api.ts
export type Todo = { id: number; title: string; done: boolean };
export async function getTodos(): Promise<Todo[]> {
const res = await fetch("http://localhost:3001/api/todos", {
// Cache on server for 30s; great default for dashboards
next: { revalidate: 30 },
});
if (!res.ok) throw new Error("Failed to fetch todos");
return res.json();
}
3) Render in a simple component
// src/app/examples/todos/page.tsx
import { getTodos } from "@/lib/api";
export default async function TodosPage() {
const todos = await getTodos();
return (
<main className="mx-auto max-w-md space-y-4 p-6">
<h1 className="text-2xl font-bold">Todos</h1>
<ul className="divide-y">
{todos.map((t) => (
<li key={t.id} className="flex items-center justify-between py-2">
<span>{t.title}</span>
<span className={t.done ? "text-green-600" : "text-yellow-600"}>
{t.done ? "done" : "pending"}
</span>
</li>
))}
</ul>
</main>
);
}
4) Add a database later (when needed)
Start with an in‑memory array to move fast. When it’s worth it, swap to any DB:
- SQL: Postgres/MySQL/SQLite via Prisma or Drizzle
- NoSQL: MongoDB
- Cache/queue: Redis
The important bit: keep your types stable and your fetch function as the integration point. Everything else can change under the hood.
Pitfalls to avoid
- Over‑modeling early. Let the UI tell you what shape the data needs.
- Fetching on the client without a reason. Server fetches are simpler and faster by default.
- Skipping errors. Throw early; Next will show a useful error overlay.
Wrap‑up
You now have a clean path: Express API → typed fetch → fast Next.js UI. Duplicate this pattern for new features and you’ll ship faster with fewer bugs.