Skip to content

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 sketch

The 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 port
  • index.ts owns side effects (DB init, listen)
  • .js extensions in imports - ESM + TypeScript quirk when moduleResolution wants 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,

-- ł.

Notes-style Express tutorial for front-end folks