@ogre.dev/framework
v1.0.6
Published
Ogres are like onions... They have layers; TypeScript framework for Node.js
Downloads
8
Maintainers
Readme
The Ogre Framework
Fast and minimalist framework for Node.js written in TypeScript.
Ogres are like onions... They have layers.
import Ogre from '@ogre.dev/framework";
const onion = new Ogre();
onion.addLayer((context, next) => {
const { response } = context;
response.setBody('What are you doing in my swamp!?');
});
onion.listen(3000);
Installation
The framework is available through an npm registry:
npm install @ogre.dev/framework
Onions and layers
Unlike most frameworks where middleware functions are called sequentially, the Ogre framework uses a layer system where the first layers in the stack wrap around the following layers, much like onions or Russian dolls.
A Layer
takes a context
and a reference to the next
layer as arguments:
const layer: Layer = (context, next) => {
...
};
Whenever a layer calls its next
parameter, the request lifecycle enters the next layer in the stack. Once the execution of the next layer has finished, the initial layer can proceed with the rest of its code.
Executing the following:
import Ogre from '@ogre.dev/framework';
const onion = new Ogre();
onion
.addLayer((context, next) => {
console.log('entering layer 0');
next();
console.log('exiting layer 0');
})
.addLayer((context, next) => {
console.log('entering layer 1');
next();
console.log('exiting layer 1');
})
.addLayer((context, next) => {
console.log('entering layer 2');
console.log('exiting layer 2');
};
onion.listen(3000);
...will print:
entering layer 0
entering layer 1
entering layer 2
exiting layer 2
exiting layer 1
exiting layer 0
Layers's next
method can only be called once. Subsequent calls are ignored.
Note that layers can be asynchronous. When in doubt, it is advised to await
the execution of the next
layer.
A typical use case would be to record the time it took to respond to the request:
import Ogre from '@ogre.dev/framework';
const onion = new Ogre();
onion
.addLayer(async (context, next) => {
const tic = new Date().getTime();
await next();
const toc = new Date().getTime();
console.log(`execution took ${toc - tic}ms`);
})
.addLayer(async () => new Promise((resolve) => setTimeout(resolve, 1000));
});
onion.listen(3000);
execution took 1002ms
Routing
Routing overview
The Ogre framework adopts a REST approach to routing with the extendable Resource
class to model HTTP resources.
For instance, let's consider the path /users/123
which targets the users
HTTP resource with a specific userId
of 123.
Here is how you would define a User
resource with a simple GET
handler:
import { Resource } from '@ogre.dev/framework';
class UserResource extends Resource {
path = '/users/{userId}';
get = (context) => {
const { request, response } = context;
response.setBody({ userId: request.pathParameters.userId });
};
}
export default new UserResource().toLayer();
Note that the userId
path variable is accessible through context.request.pathParameters
.
You can then import the resource into your app and use it as a layer:
import User from './resources/User';
...
onion.addLayer(User);
...
Paths
As we have seen in the previous section, resources revolve around their paths.
The path
property can be either a string or directly a regular expression. The former may contain path variables surrounded by brackets (e.g. {userId}
) that are made accessible through context.request.pathParameters
inside the resource layers, or an asterisk (e.g. /users/*
) to match all paths starting with the pattern preceding the symbol.
For instance /users/{userId}/*
will match /users/123
, /users/my-user
, /users/123/this/is/an/example
etc.
Best practice
The example shown in the routing overview section is intentionally simple and is only intended to help new users get started.
For more advanced applications with many resources, it is best advised to follow a modular approach with a project structure similar to the following:
main.ts
/resources
/Users
index.ts
/controllers
get.ts
post.ts
index.ts
// /resources/Users/index.ts
import { Resource } from '@ogre.dev/framework';
import { get, post } from './controllers';
class Users extends Resource {
constructor() {
super('/users');
this.get = get;
this.post = post;
}
}
export default new Users().toLayer();
// /resources/Users/controllers/get.ts
import { Layer } from '@ogre.dev/framework';
const get: Layer = (context) => {
const { response } = context;
response.setBody([/* list of users */]);
};
export default get;
// /resources/Users/controllers/index.ts
export { default as get } from './get';
export { default as post } from './post';
// main.ts
import Users from './resources/Users';
...
onion.addLayer(Users);
...
Fallback handler
You may override the fallback
method of the Resource
class with a custom layer to handle cases where the request path matched the resource's but no handlers were found for the requested HTTP method.
Error handling
The Ogre framework tries to remain as little opinionated as possible, instead leaving the user to decide what works best for their particular needs.
By default no error handling is configured. In most cases however, common errors such as 404 - Not Found
and 500 - Internal Server Error
should be dealt with. This can be achieved with the following:
// main.ts
import Ogre from '@ogre.dev/framework';
import Users from './resources/Users';
const onion = new Ogre();
onion
.addLayer(async (context, next) => {
try {
await next();
} catch (error) {
// handle 500 errors
const { response } = context;
response
.setStatus(HttpStatusCode.INTERNAL_SERVER_ERROR)
.setBody({ message: HttpStatusReason.INTERNAL_SERVER_ERROR });
}
})
.addLayer(Users)
.addLayer((context) => {
// handle 404 errors
const { response } = context;
response
.setStatus(HttpStatusCode.NOT_FOUND)
.setBody({ message: HttpStatusReason.NOT_FOUND});
});
Not that the order of the layers is important. The Internal Server Error
layer wraps the entire layer stack and catches unhandled errors while the Not Found
layer acts as the center-most layer thus intercepting requests that have not been dealt with by previous layers.
Trivia
The Ogre framework is a reference to the Shrek movie and its scene about ogres having layers, much like the framework:
Shrek: Ogres are like onions.
Donkey: They stink?
Shrek: Yes... No!
Donkey: Oh, they make you cry.
Shrek: No.
Donkey: Oh, you leave 'em out in the sun, they get all brown, start sproutin' little white hairs.
Shrek: No! Layers. Onions have layers. Ogres have layers. Onions have layers. You get it? We both have layers.
The release of the Ogre framework also coincided with the 20th anniversary of the movie.