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.

Key differences: Express is a minimal, unopinionated HTTP framework for Node.js. tekir is a full-stack framework for Bun with a first-party ORM, validation helpers, auth, and more. With Express you assemble middleware and packages yourself; with tekir most of those pieces are provided as official packages.

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'
}))