Appearance
Hello Express
Smallest useful server: import express, define a route, listen. Everything else is layers on this.
The whole app (for now)
ts
// src/index.ts
import express from 'express'
const app = express()
const PORT = 3000
app.get('/health', (_req, res) => {
res.json({ ok: true })
})
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}`)
})Run it:
bash
npx tsx src/index.ts
# or: npm run dev (if you wired tsx watch)Hit it:
bash
curl -s http://localhost:3000/health
# {"ok":true}Comments for the code above
express()creates an app instance - think "router root + middleware stack"app.get(path, handler)- only GET matches; POST would 404 hereres.json(...)setsContent-Type: application/jsonand stringifies_req- underscore says "I know it's unused" (lint-friendly)listenbinds the port; without it, nothing answers
FE mental map
| Browser | Express |
|---|---|
fetch('/health').then(r => r.json()) | app.get('/health', (req, res) => res.json(...)) |
| Network tab status 200 | default if you don't set otherwise |
Opening DevTools on localhost:5173 | opening localhost:3000 for the API |
Your Vite SPA and this server are two processes. CORS shows up later when the browser blocks cross-origin calls - chapter Env & CORS.
A slightly less tiny version
ts
import express from 'express'
const app = express()
app.get('/', (_req, res) => {
res.type('html').send('<p>API lives under /api - this is not a SPA host.</p>')
})
app.get('/api/hello', (req, res) => {
const name = typeof req.query.name === 'string' ? req.query.name : 'friend'
res.json({ message: `hey ${name}` })
})
app.listen(3000)bash
curl -s 'http://localhost:3000/api/hello?name=Ada'
# {"message":"hey Ada"}Query strings are just req.query - like URLSearchParams, but already parsed into an object.
Common first-hour footguns
- Forgot
listen- process exits or sits idle with no port - Wrong port - Vite is 5173, API is often 3000; don't mix them
- Expecting HMR - restart /
tsx watch; the browser won't hot-patch your server handlers - Opening the API URL as a "site" - JSON endpoints aren't your React app
As you can see, it's not magic. It's a labeled box that answers HTTP.
Next: routing for real - params, routers, mounting /api.
Best,
-- ł.