Express vs tekir
A detailed side-by-side comparison. For each pattern, the Express way is shown alongside both the tekir inline API and the structured approach.
Hello World
The simplest possible server. Express requires the res.json() call explicitly, while tekir auto-serializes return values.
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.json({ message: 'Hello World' })
})
app.listen(3000, () => {
console.log('Server running on port 3000')
})Middleware
Express middleware uses the (req, res, next) pattern with manual next() calls. tekir middleware receives a typed HttpContext and supports both functional and decorator-based approaches.
import express from 'express'
const app = express()
// Global middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`)
next()
})
// Per-route middleware
function auth(req, res, next) {
const token = req.headers.authorization
if (!token) return res.status(401).json({ error: 'Unauthorized' })
// verify token...
req.user = decoded
next()
}
app.get('/profile', auth, (req, res) => {
res.json(req.user)
})Route Parameters
Express params are always strings; you parse and validate them manually. tekir supports route-level constraints with regex matching and automatic type casting.
app.get('/users/:id', (req, res) => {
const id = parseInt(req.params.id, 10) // Manual parsing
if (isNaN(id)) return res.status(400).json({ error: 'Invalid ID' })
// fetch user...
res.json(user)
})
app.get('/posts/:year/:month', (req, res) => {
const year = parseInt(req.params.year, 10)
const month = parseInt(req.params.month, 10)
// No built-in validation: you must check everything
res.json({ year, month })
})JSON Body Parsing
Express requires express.json() middleware and gives you an untyped req.body. tekir parses JSON automatically and provides end-to-end type safety when combined with Zod validation.
import express from 'express'
const app = express()
// Must explicitly add body parser middleware
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.post('/users', (req, res) => {
// req.body is `any`: no type safety
const { name, email } = req.body
// Manual validation
if (!name || typeof name !== 'string') {
return res.status(400).json({ error: 'Name is required' })
}
if (!email || !email.includes('@')) {
return res.status(400).json({ error: 'Valid email is required' })
}
// create user...
res.status(201).json(user)
})Error Handling
Express requires wrapping async handlers in try/catch and forwarding errors with next(err). The global error handler must be the last middleware and must have exactly four parameters. tekir catches all errors automatically and provides a structured exception handler.
import express from 'express'
const app = express()
app.get('/users/:id', async (req, res, next) => {
try {
const user = await findUser(req.params.id)
if (!user) {
return res.status(404).json({ error: 'Not found' })
}
res.json(user)
} catch (err) {
next(err) // Must forward errors manually
}
})
// Global error handler: must be last, must have 4 params
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).json({ error: 'Internal Server Error' })
})Static Files
Express uses express.static() middleware. tekir handles static files through configuration, no middleware registration needed.
import express from 'express'
import path from 'path'
const app = express()
// Serve static files from 'public' directory
app.use(express.static('public'))
// Serve with prefix
app.use('/assets', express.static('public'))
// Serve with options
app.use(express.static('public', {
maxAge: '1d',
etag: true,
index: 'index.html'
}))