Appearance
Project anatomy
Express won't force a folder layout. That's freedom until week three, when everything lives in
index.ts. Here's a small shape that scales past "hello".
A layout that won't hate you
bash
notes-api/
├── package.json
├── tsconfig.json
├── .env # secrets - not in git
├── .env.example # safe defaults for humans
└── src/
├── index.ts # listen() only
├── app.ts # create Express app, mount middleware + routes
├── db.ts # SQLite connection + schema bootstrap
├── mail.ts # nodemailer setup
├── middleware/
│ └── auth.ts # requireAuth, attach req.user
└── routes/
├── auth.ts # /register, /login, /verify, /forgot, /reset
└── notes.ts # protected CRUD sketchThe companion in this repo follows roughly that shape: examples/notes-api.
Why split index.ts and app.ts?
ts
// src/app.ts
import express from 'express'
import { authRouter } from './routes/auth.js'
export function createApp() {
const app = express()
app.use(express.json())
app.use('/api/auth', authRouter)
return app
}ts
// src/index.ts
import { createApp } from './app.js'
import { initDb } from './db.js'
initDb()
const app = createApp()
app.listen(3000, () => {
console.log('listening on http://localhost:3000')
})Comments for the code above:
createApp()is importable in tests without opening a portindex.tsowns side effects (DB init, listen).jsextensions in imports - ESM + TypeScript quirk whenmoduleResolutionwants them
Routers vs one giant file
FE analogy: don't put every page in App.vue. Mount feature routers:
ts
app.use('/api/auth', authRouter)
app.use('/api/notes', notesRouter)Each router file stays readable. Paths stay predictable for your SPA (/api/auth/login).
Where does "business logic" go?
Early on: helpers next to the route are fine.
Later: services/users.ts or lib/tokens.ts when routes get chubby. I won't invent a DDD temple for a notes API - extract when duplication hurts.
Config lives in env
Ports, JWT secrets, mail credentials - .env, loaded once at boot. Chapter Env, config & CORS covers the FE-facing bits (CORS especially).
What I deliberately skip
- Dependency injection containers
- Hexagonal architecture folders
- Monorepo with shared packages
You can grow into those. For learning Express itself, this tree is enough.
Next we actually boot a server. 👌
Best,
-- ł.