Next Steps

This documentation assumes that you’ve followed one of the following guides to get started:

Most importantly, you have a ‘hello world’ application, and you have a reference to an app variable.

Context, Request and Response

In the ‘hello world’ example, you saw a basic middleware:

app.use((ctx: Context) => {

  ctx.response.body = {msg: 'hello world!'};


The Context object here contains all the infromation related to the HTTP request and response:

Request Response
ctx.request.path - Request URI (just the path)
ctx.request.query - Query parameters
ctx.request.method - HTTP method
ctx.request.headers - Request headers
ctx.request.body - Request body
ctx.response.status - The response status code
ctx.response.headers - Response headers
ctx.response.body - Response body

The basic purpose of a middleware is to read things from ctx.request and set things in ctx.response.

There’s also many shortcuts in ctx for things that are unambigious, such as:

  • ctx.method
  • ctx.path
  • ctx.status

This documentation does not (yet) have a complete reference to the ctx, ctx.request and ctx.response documentation. We recommend taking a look at the source to see what’s available.

Useful middleware

2 middlewares you’ll pretty much always want:

  • accesslog – makes your terminal output and server logs more useful
  • bodyparser – will let you read request bodies
npm i @curveball/accesslog @curveball/bodyparser

This is how you use them:

import { Application } from '@curveball/core';
import accessLog from '@curveball/accesslog';
import bodyParser from `@curveball/bodypaser`;

const app = Application();


On lambda or bun use @curveball/kernel instead of @curveball/core

A route and a GET request

Now that the basics are covered, let’s make a simple fake API that returns some data given a specific path.

This API responds to GET /article/:id and returns a JSON article that repeats the id in the title.

We’ll need another package:

npm i @curveball/router
import router from '@curveball/router';
import { Application } from '@curveball/core'; 

const app = Application();
app.use(router.get('/article/:id', async ctx => {

  ctx.response.body = {
    title: 'hello world ' +,
    body: 'lorum ipsum?'


// engine-specific code here

On lambda or bun use @curveball/kernel instead of @curveball/core

By default Curveball will respond with 200 OK if a response body was set. It also defaults the Content-Type to application/json.

Note that we made the middleware async. All Curveball middlewares may be async.

A PUT request on the same route

Let’s assume we also want to do a PUT request here to let people update articles.

import router from '@curveball/router';
import { Application } from '@curveball/core'; 

const app = Application();
app.use(router.put('/article/:id', async ctx => {

  const newArticleBody = ctx.request.body;

  // Store this in a database
  ctx.response.status = 204; // Success !


Error handling

It’s possible to set the error status in any middleware, as such:

app.use(async ctx => {

  if (request.body.title === undefined) {
    ctx.status = 422;
    ctx.response.body = {
      'message': 'Title was missing!'


But we recommend using the http-errors package instead:

import { UnprocessableEntity } from '@curveball/http-errors';

app.use(async ctx => {

  if (request.body.title === undefined) {
    throw new UnprocessableEntity('Title was missing!');


These exceptions can be thrown from anywhere.

We also strongly recommend using the problem middleware to automatically convert these errors to standard application/problem+json responses.

Next up: learn about the controller to better organize your code.