Building a Full‑Stack Todo List with Next.js, Prisma, and Server‑Side Rendering
This article explains how front‑end developers can expand into full‑stack development by using Next.js with server‑side rendering, Prisma ORM, and SQLite to create a functional Todo List application, covering installation, database schema, server actions, and client‑side form handling.
In 2024 front‑end development is no longer limited to UI components; mastering TypeScript and server‑side rendering (SSR) frameworks like Next.js is becoming essential. The article argues that front‑end engineers can broaden their skill set by handling back‑end tasks with Node.js, especially for lightweight internal tools, blogs, and simple services.
It lists typical scenarios where a front‑end developer can apply SSR: corporate websites with CMS, blog systems, internal tool platforms, stable lightweight services (e.g., psychological tests, online utilities), and some outsourcing projects.
The advantages of modern SSR frameworks are highlighted:
Improved SEO and high‑performance page access.
Reduced front‑end bundle size by keeping libraries on the server.
Built‑in database connectivity for simple single‑database workloads.
Ability to ship a single full‑stack package to clients, lowering adoption cost.
Strong platform support (e.g., Vercel) even without dedicated servers.
To demonstrate these concepts, a Todo List project is built with Next.js and Prisma.
Installation of Next.js:
npx create-next-app@latest
What is your project named? todo
Would you like to use TypeScript? No / Yes #yes
Would you like to use ESLint? No / Yes # no
Would you like to use Tailwind CSS? No / Yes #yes
Would you like to use `src/` directory? No / Yes #yes
Would you like to use App Router? (recommended) No / Yes # yes
Would you like to customize the default import alias (@/*)? No / Yes # yes
What import alias would you like configured? @/*Install Prisma and Day.js:
npm i prisma dayjs -D
npx prisma init --datasource-provider sqlite # you can choose other DBsSQLite is used as a lightweight relational database. After initialization, the prisma/schema.prisma file defines the Todo model:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Todo {
id Int @id @default(autoincrement())
name String
time String
created DateTime @default(now())
}Running prisma db push creates dev.db and installs @prisma/client for database access.
Next, a singleton Prisma client is exported ( src/app/prisma.ts ) to avoid multiple connections during development:
import { PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => new PrismaClient()
type PrismaClientSingleton = ReturnType
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClientSingleton }
const prisma = globalForPrisma.prisma ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismaA client‑side form component ( src/app/form.tsx ) is created with 'use client' to enable React interactivity:
'use client'
import { useCallback } from "react"
import { addTodo } from "./data"
export function Form() {
const submit = useCallback(async (e: React.MouseEvent
) => {
e.preventDefault()
const formData = new FormData(e.target as HTMLFormElement)
await addTodo({
name: formData.get('name') as string,
time: formData.get('time') as string
})
location.reload()
}, [])
return (
添加
)
}The server‑side action that writes to the database ( src/app/data.ts ) uses Next.js Server Action feature:
'use server'
import prisma from "./prisma"
export const addTodo = async (data: {name: string, time: string}) => {
await prisma.todo.create({ data })
}The main page ( src/app/page.tsx ) fetches todos on the server, formats dates with Day.js, and renders the list together with the Form component:
import dayjs from "dayjs"
import { Form } from "./form"
import prisma from "./prisma"
const getTodos = () => prisma.todo.findMany({ select: { id: true, name: true, time: true, created: true } })
export default async function Home() {
const todos = await getTodos()
return (
{todos.map(item => (
{item.time}
执行
{item.name}
创建时间:
{dayjs(item.created).format('YYYY-MM-DD HH:mm')}
))}
)
}Because the page component is a server component by default, the database query runs on the server, keeping the client bundle small. The final UI shows a list of todos and a form to add new items, all rendered server‑side.
The article concludes that with tools like Next.js and Prisma, front‑end developers can quickly build full‑stack applications without needing heavyweight back‑end frameworks such as NestJS, Docker, or distributed databases, enabling rapid delivery of value to companies and clients.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.