Skip to content

Instantly share code, notes, and snippets.

@brigand
Last active January 14, 2022 14:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brigand/c703c470612ae45506cb390a43e35193 to your computer and use it in GitHub Desktop.
Save brigand/c703c470612ae45506cb390a43e35193 to your computer and use it in GitHub Desktop.
express-saga is a web framework based on express and the saga pattern (redux-saga's interpretation) – just an idea, let's see if it turns out decent :-)

This is an idea, the code doesn't exist

express-saga is a web framework based on express and the saga pattern (redux-saga's interpretation).

You can use all existing express middleware without issue, but often there are better alternatives in express-saga.

Most effects from redux-saga are available, along with new effects for web server development.

This library is designed with a heavy focus on unit-testability. You don't need module level mocks due to dependency injection. Your routers are pure functions, so you don't need to run a http server in tests.

It also supports hot module replacement for far fewer in-development server restarts, without special build tools.

Example

First we'll set up a development server with hot-module-replacement. We're using mongo, but this will work with any database. This file only runs once, so create persistent objects here. When you change this file, restart the server.

server.js:

const express = requre('express');
const { MongoClient } = require('mongodb');
const hot = require('express-saga/hot');
const app = express();

function getSaga() {
  const saga = require('./rootSaga');
  return saga;
}

const mongo = new MongoClient();
mongo.connect(`mongodb://localhost:27017/myproject`).then((db) => {
  const services = { todos: db.collection('todos') };
  app.use(hot.saga(getSaga, services));
  app.listen(process.env.PORT || 4000);
});

Then rootSaga.js is the main app. We build and export a root saga which delegates work to the other sagas.

const $ = require('express-saga');

const staticSaga = function* (req, res, next) {
  const path = yield $.resolveStatic(`{__dirname}/public`, req.url);
  if (!path) return next();
  yield $.cors('*');
  yield $.send.file(path);
  yield $.end();
}

const getTodosSaga = function* () {
  const todoService = yield $.service('todos');

  // note that $.call takes a function; this aids in testing because
  // you can choose to call the function or not in tests
  const data = yield $.call(() => todoService.find({}).toArray());

  // send back as json, defaults to 200 status code
  yield $.send.json(data);
};

const createTodoSaga = function* () {
  const todoService = yield $.service('todos');

  // body parsing is done as-needed
  // this ensures you only accept the format you expect
  const body = yield $.body.json();
  try {
    yield $.call(() => todoService.create(body));
    yield $.send.json({ success: true });
  } catch (e) {
    yield $.status(400);
    yield $.send.json({ success: false });
  }
};

const apiRouter = function* (req, res, next) {
  const routes = [
    $.get('/todos', getTodosSaga),
    $.post('/todos', createTodoSaga),
  ];
  yield $.routes(routes, req, res, next);
};

const rootSaga = function*(req, res, next) {
  const routes = [
    $.get('*', staticSaga),
    $.router('/api', apiRouter),
  ];

  yield $.routes(routes, req, res, next);
}

module.exports = rootSaga;

Then run node server.js, or env SAGA_DEBUG='*' node server.js.

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