Note: this is just a concept, it'll probably never exist or be significantly different.
To start, we'll create a basic server with two routes. Note that none of these
generator functions have side effects. They yield
effect descriptors. This
allows easy unit testing without mocking modules.
const gs = require('genserve');
const config = {
port: 3000,
};
const router = gs.Router();
const rateLimiter = gs.RateLimiter({store: 'redis', backupStore: 'memory'});
router.get('/greet', function* (ctx) {
yield rateLimiter.run({ctx, perHour: 10});
yield ctx.json({message: 'Hello, world!'});
});
router.post('/comments', function* (ctx) {
yield rateLimiter.run({ctx, spacing: '5 minutes'});
try {
const user = yield ctx.assertUser();
const body = yield ctx.bodyAsJson();
const res = yield gs.call(api.postComment, user, body);
yield ctx.sendJson(res);
} catch (e) {
yield ctx.sendError(e);
}
});
const app = gs.app(config, function* (ctx) {
yield* router.handleRequest(ctx);
});
genserve has many features that aid in building apps with complex requirements.
The RateLimiter
provides highly flexible rate limiting on a per-route or bucket of
routes basis: collectively "resources". Here's the config options for it.
Defaults can be defined in the RateLimiter
factory. You can have many RateLimiter
instances.
store
: the key-value store used for rate limiting. You can pass a custom object implementing the interfacebackupStore
: if thestore
is unavailable, use the backup. If neither can be reached, the server crashes.perDay
,perHour
,perMinute
,perSecond
: max requests for the current resource in a sliding time windowspacing
: two requests cannot occur in a given time window
A simple caching solution is provided that allows storing data in redis or similar.
const cache = gs.makeCache(redisClient);
router.get('/trending/:page', function* (ctx) {
const cacheKey = ['trending', ctx.params.page];
const cahceResult = yield cache.get(cacheKey);
if (cacheResult) {
yield ctx.sendJson(cacheResult);
return;
}
const data = yield fetchTrending(ctx.params.page);
yield cache.store(cacheKey, data, {maxAge: 60 * 60 * 1000});
yield ctx.sendJson(data);
});
TODO: maybe passport integration? Might include default schemas for sql and mongodb.
The first argument to gs.app
is a config object.
The port to use if possible. A number or string is fine.
If the port is unavailable, this allows specifying a backup. Mostly only used in development.
The value 'true' indicates it should try up to 10 ports before giving up.
The value of 'interactive' prompts the terminal for a port number or 'y' indicating it should auto-find a port.
When set to true
the terminal will become an interactive application that allows
inspecting individual requests, seeing logs, and other useful tools to help in development.