core

by curveball

curveball /core

The Curveball framework is a TypeScript framework for node.js with support for modern HTTP features.

445 Stars 8 Forks Last release: 7 days ago (v0.14.3) MIT License 302 Commits 36 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

Curveball

Curveball is a framework for building web services in Node.js. It fullfills a similar role to Express and it's heavily inspired by Koa.

This web framework has the following goals:

  • A minimal foundation.
  • Completely written in and for TypeScript.
  • Modern Ecmascript features.
  • Async/await-based middleware.
  • Native support for HTTP/2, including easy access to HTTP/2 Push.
  • Native, deeply integrated Websocket.
  • Native support for modern HTTP features, such as
    103 Early Hints
    .
  • The ability to easily do internal sub-requests without having to do a real HTTP request.

If you used Koa in the past, this is going to look pretty familiar. I'm a big fan of Koa myself and would recommend it over this project if you don't need any of the things this project offers.

Installation

npm install @curveball/core

Getting started

Curveball only provides a basic framework. Using it means implementing or using curveball middleware. For example, if you want a router, use or build a Router middleware.

All of the following examples are written in typescript, but it is also possible to use the framework with plain javascript.

import { Application, Context } from '@curveball/core';

const app = new Application(); app.use((ctx: Context) => {

ctx.status = 200; ctx.response.body = 'Hello world!'

});

app.listen(4000);

Middlewares you might want

AWS Lambda support

See aws-lambda.

Project status

The project is currently in beta. It might go through a few more changes, but the project is mostly stable. It's ready for production.

Doing internal subrequests

Many Node.js HTTP frameworks don't easily allow doing internal sub-requests. Instead, they recommend doing a real HTTP request. These requests are more expensive though, as it has to go through the network stack.

Curveball allows you do do an internal request with 'mock' request and response objects.

Suggested use-cases:

  • Running cheaper integration tests.
  • Embedding resources in REST apis.

Example:

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

const app = new Application(); const response = await app.subRequest('POST', '/foo/bar', { 'Content-Type': 'text/html' }, '

Hi

');

Only the first 2 arguments are required. It's also possible to pass a Request object instead.

import { Application, MemoryRequest } from '@curveball/core';

const app = new Application(); const request = new MemoryRequest('POST', '/foo/bar', { 'Content-Type': 'text/html' }, '

Hi

'); const response = await app.subRequest(request);

HTTP/2 push

HTTP/2 push can be used to anticipate GET requests client might want to do in the near future.

Example use-cases are:

  • Sending scripts and stylesheets earlier for HTML-based sites.
  • REST api's sending resources based on relationships clients might want to follow.
import { Application } from '@curveball/core';
import http2 from 'http2';

const app = new Application(); const server = http2.createSecureServer({ key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem') }, app.callback()); server.listen(4443);

app.use( ctx => {

ctx.response.status = 200; ctx.response.headers.set('Content-Type', 'text/html'); ctx.response.body = '';

await ctx.response.push( pushCtx => {

pushCtx.path = '/script.js';
return app.handle(pushCtx);

});

});

HTTP/2 push works by sending HTTP responses to the client, but it also includes HTTP requests. This is because HTTP clients need to know which request the response belongs to.

The

push
function simply takes a middleware, similar to
use
on Application. The callback will only be triggered if the clients supports push and wants to receive pushes.

In the preceding example, we are using

app.handle()
to do a full HTTP request through all the regular middlewares.

It's not required to do this. You can also generate responses right in the callback or call an alternative middleware.

Lastly,

pushCtx.request.method
will be set to
GET
by default.
GET
is also the only supported method for pushes.

Sending 1xx Informational responses

Curveball has native support for sending informational responses. Examples are:

  • 100 Continue
    to let a client know even before the request completed that it makes sense to continue, or that it should break off the request.
  • 102 Processing
    to periodically indicate that the server is still working on the response. This might not be very useful anymore.
  • 103 Early Hints
    a new standard to let a client or proxy know early in the process that some headers might be coming, allowing clients or proxies to for example pre-fetch certain resources even before the initial request completes.

Here's an example of a middleware using

103 Early Hints
:
import { Application, Context, Middleware } from '@curveball/core';

const app = new Curveball(); app.use(async (ctx: Context, next: Middleware) => {

await ctx.response.sendInformational(103, { 'Link' : [ ' rel="prefetch" as="style"', ' rel="prefetch" as="script"', ] }); await next();

});

Websocket

To get Websocket up and running, just run:

app.listenWs(port);

This will start a websocket server on the specified port. Any incoming Websocket connections will now just work.

If a Websocket connection was started, the

Context
object will now have a
webSocket
property. This property is simply an instance of Websocket from the ws NPM package.

Example usage:

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

app.use( ctx => { if (!ctx.webSocket) { throw new UpgradeRequired('This endpoint only supports WebSocket'); }

ctx.webSocket.send('Hello'); ctx.webSocket.on('message', (msg) => { console.log('Received %s', msg); });

});

If you use typescript, install the

@types/ws
package to get all the correct typings:
npm i -D @types/ws

The Controller package also has built-in features to make this even easier.

API

The Application class

The application is main class for your project. It's mainly responsible for calling middlewares and hooking into the HTTP server.

It has the following methods

  • use(m: Middleware)
    - Add a middleware to your application.
  • handle(c: Context)
    - Take a Context object, and run all middlewares in order on it.
  • listen(port: number)
    - Run a HTTP server on the specified port.
  • listenWs(port: number)
    - Start a websocket server on the specified port.
  • callback()
    - The result of this function can be used as a requestListener for node.js
    http
    ,
    https
    and
    http2
    packages.
  • subRequest(method: string, path:string, headers: object, body: any)
    - Run an internal HTTP request and return the result.
  • subRequest(request: Request)
    - Run an internal HTTP request and return the result.

The Context class

The Context object has the following properties:

  • request
    - An instance of
    Request
    .
  • response
    - An instance of
    Response
    .
  • state
    - An object you can use to store request-specific state information. this object can be used to pass information between middlewares. A common example is that an authentication middlware might set 'currently logged in user' information here.
  • ip()
    - Get the
    ip
    address of the HTTP client that's trying to connect.
  • path
    - The path of the request, for example
    /foo.html
    .
  • method
    - For example,
    POST
    .
  • query
    - An object containing the query parametes.
  • status
    - The HTTP status code, for example
    200
    or
    404
    .
  • sendInformational(status, headers?)
    - Sends a
    100 Continue
    ,
    102 Processing
    or
    103 Early Hints
    - response with optional headers.
  • push(callback: Middleware)
    - Do a HTTP/2 push.
  • redirect(status, location)
    - Send a redirect status code and set a
    Location
    header.

The Request interface

The Request interface represents the HTTP request. It has the following properties and methods:

  • headers
    - An instance of
    Headers
    .
  • path
    - The path of the request, for example
    /foo.html
    .
  • method
    - For example,
    POST
    .
  • requestTarget
    - The full
    requestTarget
    from the first line of the HTTP request.
  • body
    - This might represent the body, but is initially just empty. It's up to middlewares to do something with raw body and parse it.
  • rawBody()
    - This function uses the raw-body function to parse the body from the request into a string or Buffer. You can only do this once, so a middleware should use this function to populate
    body
    .
  • query
    - An object containing the query parametes.
  • type
    - The
    Content-Type
    without additional parameters.
  • accepts
    - Uses the accepts package to do content-negotiation.
  • is(contentType)
    - Returns true or false if the
    Content-Type
    of the request matches the argument. If your
    Content-Type
    is
    application/hal+json
    it will return true for
    application/hal+json
    ,
    hal+json
    and
    json
    .

The Response interface

The Response interface represents a HTTP response. It has the following properties and methods:

  • headers
    - An instance of
    Headers
    .
  • status
    - The HTTP status code, for example
    200
    or
    404
    .
  • body
    - The response body. Can be a string, a buffer or an Object. If it's an object, the server will serialize it as JSON.
  • type
    - The
    Content-Type
    without additional parameters.
  • sendInformational(status, headers?)
    - Sends a
    100 Continue
    ,
    102 Processing
    or
    103 Early Hints
    - response with optional headers.
  • push(callback: Middleware)
    - Do a HTTP/2 push.
  • is(contentType)
    - Returns true or false if the
    Content-Type
    of the response matches the argument. If your
    Content-Type
    is
    application/hal+json
    it will return true for
    application/hal+json
    ,
    hal+json
    and
    json
    .
  • redirect(status, location)
    - Send a redirect status code and set a
    Location
    header.

The Headers interface

The Headers interface represents HTTP headers for both the

Request
and
Response
.

It has the following methods:

  • set(name, value)
    - Sets a HTTP header.
  • get(name)
    - Returns the value of a HTTP header, or null.
  • has(name)
    - Returns true or false if the header exists.
  • delete(name)
    - Deletes a HTTP header.
  • append(name, value)
    - Adds a HTTP header, but doesn't erase an existing one with the same name.
  • getAll()
    - Returns all HTTP headers as a key-value object.

Other features

Use the

checkConditional
function to verify the following headers:
  • If-Match
  • If-None-Match
  • If-Modified-Since
  • If-Unmodified-Since
    .

Signature:

checkConditionial(req: RequestInterface, lastModified: Date | null, etag: string | null): 200 | 304 : 412;

This function returns

200
if the conditional passed. If it didn't, it will return either
304
or
412
. The former means you'll want to send a
304 Not Modified
back, the latter
412 Precondition Failed
.

200
does not mean you have to return a
200 OK
status, it's just an easy way to indicate that all all conditions have passed.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.