How app.ts Creates the HTTP Server and Defines Routes in JSON Server

The src/app.ts file in JSON Server constructs a Tinyhttp application instance, configures essential middleware for static files, CORS, and JSON parsing, and registers a complete set of RESTful routes that delegate CRUD operations to the Service layer.

JSON Server’s architecture separates the HTTP layer from the CLI lifecycle. The typicode/json-server repository implements the core web logic in src/app.ts, which exports a factory function createApp. This function builds a fully configured Tinyhttp application that transforms a LowDB database instance into a mock REST API, while the actual server startup is handled separately by src/bin.ts.

Creating the Tinyhttp Application in app.ts

Instantiating the App Class

At the heart of the HTTP server creation is the Tinyhttp framework. Inside src/app.ts at line 95, the code initializes the application:

const app = new App()

The App class is imported from @tinyhttp/app, a lightweight Node.js framework. This app instance serves as the container for all middleware and route definitions. The function containing this instantiation is exported as createApp, allowing both the CLI and programmatic consumers to generate a configured server instance without immediately binding it to a network port.

Configuring Middleware for the HTTP Server

Before defining routes, app.ts layers several middleware functions to handle cross-cutting concerns. Each middleware is registered using Tinyhttp’s app.use() pattern.

Static File Serving

JSON Server serves a built-in web UI and supports custom static directories. Lines 98-101 configure this:

app.use(serveStatic('public'))
staticArr.forEach((dir) => {
  app.use(serveStatic(dir))
})

The public folder contains the default HTML interface, while additional directories passed via the CLI --static flag are mounted iteratively.

CORS Handling

To support cross-origin requests, lines 104-113 implement dynamic CORS middleware. Rather than using a static configuration, it inspects the incoming Access-Control-Request-Headers and mirrors them back in the Access-Control-Allow-Headers response. This ensures the mock API accepts the specific headers requested by the client.

JSON Body Parsing

At line 115, the application gains the ability to parse JSON request bodies:

app.use(json())

This middleware from @tinyhttp/json parses application/json payloads and assigns the result to req.body, making it available to subsequent route handlers.

Template Engine Setup

For the root UI page, JSON Server uses the Eta template engine. Lines 117-118 configure the engine and set the views directory:

app.engine('eta', eta.renderFile)
app.set('views', 'views')

This allows the route handler for GET / to render views/index.html with the current database state.

Defining RESTful Routes in app.ts

With middleware configured, app.ts registers the full spectrum of REST endpoints. Each route delegates its logic to methods from src/service.ts, which implements the actual CRUD operations against the LowDB database.

List and Single Resource Retrieval

Collection listing and individual resource retrieval are handled by parameterized routes. Lines 119-130 define the collection list endpoint:

app.get('/:name', (req, res) => {
  const { name } = req.params
  const query = parseListParams(req.query, db.data[name])
  res.locals['data'] = service.find(name, query)
})

The parseListParams function (lines 30-66) processes query string parameters like _sort, _page, _per_page, and _where to build filtering and pagination logic. For single resources, lines 133-137 handle GET /:name/:id by calling service.findById.

Create, Update, and Delete Operations

Mutation routes use helper wrappers to validate inputs before calling service methods. Line 139 shows the create endpoint:

app.post('/:name', withBody, (req, res) => {
  res.locals['data'] = service.create(req.params.name, res.locals['body'])
})

The withBody middleware validates that req.body is a valid item. Similarly, PUT and PATCH routes use withBody for collection updates and withIdAndBody for single item updates. Lines 149-152 handle deletion:

app.delete('/:name/:id', (req, res) => {
  const { name, id } = req.params
  service.destroyById(name, id, req.query['_dependent'])
  res.locals['data'] = {}
})

Query Parameter Parsing

The parseListParams function (lines 30-66) is critical for the list endpoints. It interprets special query parameters:

  • _sort and _order for sorting
  • _page and _per_page for pagination
  • _embed for sideloading related resources
  • _where for complex filtering

This transforms raw query strings into a structured query object passed to service.find.

Starting the Server from bin.ts

A crucial architectural detail is that app.ts does not start the server itself. This separation allows the application logic to be tested or embedded without binding to a port. The CLI entry point in src/bin.ts handles server startup:

// src/bin.ts – lines 42-44
const app = createApp(db, { logger: false, static: staticArr });
app.listen(port, () => { 
  // pretty console output 
});

The createApp function exported from app.ts returns the fully configured Tinyhttp instance. Calling app.listen() (provided by Tinyhttp) creates the underlying Node.js HTTP server and begins accepting connections.

Programmatic Usage Example

Because app.ts exports a factory function rather than a singleton, you can embed JSON Server in your own applications:

import { Low } from 'lowdb'
import { JSONFile } from 'lowdb/node'
import { createApp } from './src/app.js'

// 1️⃣ Initialise an in‑memory DB (no file needed for demo)
const adapter = new JSONFile<{ posts: any[] }>('tmp-db.json')
const db = new Low(adapter, { posts: [] })
await db.read()

// 2️⃣ Build the Tinyhttp app
const app = createApp(db, { logger: true })

// 3️⃣ Start listening (e.g., port 4000)
app.listen(4000, () => console.log('JSON Server listening on http://localhost:4000'))

// Now you can call:
// GET http://localhost:4000/posts
// POST http://localhost:4000/posts   { "title": "Hello" }

This pattern demonstrates how app.ts creates a reusable HTTP server configuration independent of the CLI lifecycle.

Summary

  • src/app.ts constructs a Tinyhttp application instance via new App() and exports it as a factory function createApp, enabling both CLI and programmatic usage.
  • The file configures middleware for static file serving, dynamic CORS, JSON body parsing, and the Eta template engine before any routes are defined.
  • RESTful routes are registered for all CRUD operations (GET, POST, PUT, PATCH, DELETE), delegating business logic to src/service.ts and using helper functions like withBody and parseListParams for validation and query parsing.
  • The actual HTTP server startup occurs in src/bin.ts, which calls app.listen() after receiving the configured app from createApp, maintaining a clean separation between application logic and server lifecycle.

Frequently Asked Questions

What is the difference between app.ts and bin.ts in JSON Server?

src/app.ts is responsible for building and configuring the Tinyhttp application instance, including all middleware and route definitions, but it does not start the server. src/bin.ts acts as the CLI entry point that parses command-line arguments, initializes the LowDB database, calls createApp() to get the configured app, and then invokes app.listen() to bind to a port and start accepting HTTP requests.

How does app.ts handle CORS requests?

Rather than using a static CORS configuration, app.ts implements dynamic CORS middleware between lines 104-113. It inspects the incoming request's Access-Control-Request-Headers header and mirrors those specific headers back in the Access-Control-Allow-Headers response. This ensures the mock API accepts exactly the headers requested by the client without hardcoding allowed origins or methods.

Can I use app.ts to create a JSON Server instance programmatically?

Yes, app.ts exports a createApp factory function specifically designed for programmatic usage. You can import createApp into your own Node.js application, pass it a LowDB instance and configuration options (such as { logger: true, static: [] }), and receive a fully configured Tinyhttp app. This allows you to embed JSON Server's REST API functionality into existing applications or test suites without using the CLI.

What routing library does JSON Server use under the hood?

JSON Server uses Tinyhttp (@tinyhttp/app), a lightweight, Express-like Node.js framework, as its routing library. The App class from Tinyhttp provides the use(), get(), post(), and other HTTP method functions used in app.ts to define middleware and routes. This choice keeps the codebase minimal while providing robust routing capabilities compatible with Express middleware patterns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →