Raptor is a lightweight middleware framework for Deno, focusing on readability and clear code. It balances functionality and simplicity, enabling you to express complex logic concisely.
Note
This is currently under heavy development and is not yet suitable for production use. Please proceed with caution.
To start using Raptor, simply install it through the CLI or import it directly from JSR.
deno add jsr:@raptor/framework
Raptor is also available to import directly via JSR: https://jsr.io/@raptor/framework
Raptor's kernel will run through and process each middleware in the order they were added to the stack. However, if you wish to process the next middleware within another, you can use the built-in next
argument which is provided to the callback (see "Calling the next middleware").
// main.ts
import { type Context, Kernel } from "jsr:@raptor/framework";
const app = new Kernel();
app.add(() => 'Hello, Dr Malcolm!');
app.serve({ port: 8000 });
To run this code with Deno:
> deno run --allow-net main.ts
In Raptor, at least one middleware function is required to return a body response to ensure that the request cycle completes successfully. The Content-Type
of your response will be automatically determined by the framework, allowing you to focus on your own application code.
If the middleware response body is an object, it will be automatically recognized as application/json
. Consequently, both the Content-Type
header and the response body will be appropriately set to reflect this format.
app.add(() => ({
name: 'Dr Ian Malcolm',
}));
When a string is returned, the Content-Type
header is automatically set to text/plain
. However, if the string is detected to contain HTML, the Content-Type
header will be automatically adjusted to text/html
.
app.add(() => '<h1>Hello, Dr Malcolm!</h1>');
Although it's convenient to return data without configuring a Content-Type
, there may be instances where you need to specify a particular header. In such cases, you can proceed as follows:
app.add((context: Context) => {
context.response.headers.set('Content-Type', 'application/hal+json');
return {
name: 'Dr Ian Malcolm'
}
});
The context object is passed as the first parameter to a middleware function. It includes the HTTP request, the HTTP response, and any additional properties added by previous middleware calls.
The next middleware function is responsible for invoking the subsequent middleware in the stack. It must be called if the current middleware doesn't handle the request and provide a response; otherwise, the system will respond with an error. As an example, the following script demonstrates how to calculate runtime across two middleware:
app.add(async (_context: Context, next: CallableFunction) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ms}ms`);
});
app.add(async () => {
await new Promise(resolve => setTimeout(resolve, 3000));
return 'Hello, Dr Malcolm!';
});
// console: ~3004ms
Errors thrown in middleware are picked up and added to the Context
object, allowing you to catch and handle them in a final middleware callback. By default, the system automatically catches errors for you. If you wish to change this behaviour, you can adjust the settings in the Kernel options (see below). As with standard middleware responses, content types and HTTP status codes are automatically assigned, but you can override them if needed.
The following errors are currently available to import and throw from within the framework:
NotFound
BadRequest
ServerError
TypeError
You can create your own errors by implementing the Error
interface.
If you prefer to manage errors manually, you can disable the automatic error catching feature in the Kernel options. When you do this, it's essential to add a middleware callback (see example below) to check for errors and handle the responses accordingly.
import { type Context, Kernel, NotFound } from "jsr:@raptor/framework";
// Disable automatic error catching.
const app = new Kernel({
catchErrors: false,
});
// Simulate an application error.
app.add(() => {
throw new NotFound();
});
// Catch our error and handle response.
app.add((context: Context) => {
const { error, response } = context;
if (error?.status === 404) {
return {
message: 'No page could be found'
}
}
response.status = 500;
return {
message: 'There was an internal server error'
}
});
app.serve({ port: 8000 });
Raptor was built with Deno in mind, meaning that deploying to Deno Deploy is easy. Within your root directory (alongside your main.ts
entry file) you can run:
deployctl deploy
You can also deploy your application to a Cloudflare Worker by simply using the respond
method of the Kernel. This allows you to bypass the usual serve
method and return a Response directly.
/* ... */
export default {
fetch: (request: Request) => {
return kernel.respond(request);
}
}
Once you've setup your application for Cloudflare Workers, you can run the deployment as usual:
npx wrangler deploy
Raptor is built as an unopinionated modular framework, giving you the flexibility to choose which components to include. Below is a list of available first-party extensions:
- Router Middleware: https://jsr.io/@raptor/router
Copyright 2024, @briward. All rights reserved. The framework is licensed under the MIT license.