Core
The Curveball framework is a TypeScript framework for node.js with support for modern HTTP features.
Install / Use
/learn @curveball/CoreREADME
Curveball
Curveball is a framework for building web services in Node.js. It fullfills a similar role to [Express][1] and it's heavily inspired by [Koa][2].
This web framework has the following goals:
- A minimal foundation.
- Completely written in and for [TypeScript][3].
- Modern Ecmascript features.
- Async/await-based middleware.
- Support for Node, Bun, AWS Lambda, Azure functions.
- 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][http-103]. - 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
- Router
- Body Parser
- [Controller][controller]
- Access Logs
- Sessions
- Generating application/problem+json responses
- CORS
- Hypermedia Links
- Server-rendered React support
- Serving static files
- JSON-Schema validation
Authentication
You might like a12n-server, a full OAuth2 authorization server, written in Curveball and works well with the OAuth2 middleware.
AWS Lambda support / 'Serverless'
Bun support
To use Curveball with Bun, use the kernel package:
import { Application } from '@curveball/kernel';
const app = new Application();
// Add all your middlewares here!
app.use( ctx => {
ctx.response.body = {msg: 'hello world!'};
});
export default {
port: 3000,
fetch: app.fetch.bind(app)
};
Some more details can be found in this article.
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 to 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' }, '<h1>Hi</h1>');
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' }, '<h1>Hi</h1>');
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][http-100] 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][http-102] to periodically indicate that the server is still working on the response. This might not be very useful anymore. - [
103 Early Hints][http-103] 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' : [
'</style.css> rel="prefetch" as="style"',
'</script.js> 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][ws]
from the [ws][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][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.jshttp,httpsandhttp2packages.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.origin- Sets the 'origin' for the application. This is used to determine absolute URIs. You can set theorigindirectly on the application, but you can also set aCURVEBALL_ORIGINenvironment variable. If nothing is set this value will default tohttp://localhost.
The Context class
The Context object has the following properties:
request- An instance ofRequest.response- An instance ofResponse.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 theipaddress 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 example200or404.sendInformational(status, headers?)- Sends a100 Continue,102 Processingor103 Early Hints- response with optional headers.push(callback: Middleware)- Do a HTTP/2 push.redirect(status, location)- Send a redirect status code and set aLocationheader.absoluteUrl- The absolute URL
