Skip to content

Instantly share code, notes, and snippets.

@brigand
Last active January 13, 2017 15:00
Show Gist options
  • Save brigand/ef6166cac17d69ee6f13f1b3dc60bad6 to your computer and use it in GitHub Desktop.
Save brigand/ef6166cac17d69ee6f13f1b3dc60bad6 to your computer and use it in GitHub Desktop.
genserve - a concept for a generator-based server with pure controllers and many features built in

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);
});

Features

genserve has many features that aid in building apps with complex requirements.

Rate Limiting

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 interface
  • backupStore: if the store 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 window
  • spacing: two requests cannot occur in a given time window

Caching

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);
});

Authorization

TODO: maybe passport integration? Might include default schemas for sql and mongodb.

Root config

The first argument to gs.app is a config object.

port

The port to use if possible. A number or string is fine.

tryOtherPorts

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.

interactive

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment