
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
simple-express-framework
Advanced tools
> Simple micro framework based on Express, allowing you to prototype APIs blazingly quickly
Simple micro framework based on Express, allowing you to prototype APIs blazingly quickly

Micro-framework that let's you create more readable route structure, use simple async functions as route handlers, with clear error handling and run fully functional express app in seconds.
npm i simple-express-framework
import simpleExpress from 'simple-express-framework';
simpleExpress({
port: 8080,
routes: [
['/hello', {
get: () => ({ status: 200, body: 'Hello world' }),
}],
],
});
And that's all! Express server, listening on selected port, with reasonable default settings is up and running in seconds!
But there's more! Dive in in the Examples section to see the power of simple and readable route handlers, clear error handling and more - everything just works and is nearly infinitely expandable thanks to plugin support!
You run the app by executing simpleExpress function. All options are optional.
import simpleExpress from 'simple-express-framework';
simpleExpress({
port: 8080,
routes,
expressMiddleware,
middleware,
errorHandlers,
config,
routeParams,
app,
server,
})
.then(({ app, server }) => console.log('App started :)'))
.catch(error => console.error('App starting failed :(', error));
Simple express accepts the following options:
Resolves to an object with the following fields:
SimpleExpress handlers are similar to express handlers except they accept one argument: object with the following fields:
Handlers should return falsy value (to not send any response) or the response object:
res.type() express method which sets the Content-Type HTTP header to the value received if it contains '/' (e.g. application/json), or determines the mime type by mime.lookup()res.json methodres.send methodTo pass an error to error handlers, handler can either pass an error to next() function:
({ next }) => {
next(new Error('Something went wrong'));
};
Or just return an Error object (or instance of a class extending Error)
() => {
return new Error('Something went wrong');
};
Or throw an Error (or instance of a class extending Error)
() => {
throw new Error('Something went wrong');
};
({ req, res, params, body, query, originalUrl, protocol, locals, xhr, getHeader, next }) => {
...
return {
status: 500,
body: { message: 'Server error' },
headers: { customHeader: 'customHeaderValue' },
};
};
({ params, body }) => {
...
return {
status: 301,
redirect: '/test',
};
};
({ params, body }) => {
...
return {
body: Buffer.from('LoremIpsum')
};
};
({ res, next }) => {
...
res.sendFile('path/to/file', error => {
if (error) {
next(error);
}
});
};
All handlers can work as middlewares if they trigger next() instead of returning a response. You can pass an array of handlers. They will be executed in order just like express middlewares.
{
get: [
({ getHeader, locals, next }) => {
const user = verifyToken(getHeader('authentication'))
if (user) {
locals.user = user;
return next();
}
// the same as "next(new AuthenticationError('Unauthenticated'))"
return new AuthenticationError('Unauthenticated');
},
({ locals }) => ({
body: 'You are logged in as ' + locals.user.username,
}),
]
}
Middlewares can be chained in different ways:
[
[
'/foo',
({ getHeader, locals, next }) => {
const user = verifyToken(getHeader('authentication'))
if (user) {
locals.user = user;
return next();
}
// the same as "next(new AuthenticationError('Unauthenticated'))"
return new AuthenticationError('Unauthenticated');
},
{
get: ({ locals }) => ({
body: 'You are logged in as ' + locals.user.username,
}),
}
]
]
[
'/foo',
[
({ getHeader, locals, next }) => {
const user = verifyToken(getHeader('authentication'))
if (user) {
locals.user = user;
return next();
}
// the same as "next(new AuthenticationError('Unauthenticated'))"
return new AuthenticationError('Unauthenticated');
},
{
get: ({ locals }) => ({
body: 'You are logged in as ' + locals.user.username,
}),
}
]
]
All errors are being caught by the simpleExpress and passed to error handlers. Error handlers are identical to handlers, except they receive error as first argument, and object of handler parameters as second. To pass an error to error handlers you can trigger next() with an error as an argument, throw an error, or return an error in a handler.
Please note, that handler parameters object is exactly the same as in case of handlers, except 'params' field which is for whatever reason stripped by express.
routes: [
['/foo', {
get: () => {
throw new AuthenticationError();
}
}],
['/bar', {
get: () => new AuthenticationError(),
}],
['/baz', {
get: ({ next }) => next(new AuthenticationError()),
}]
]
errorHandlers: [
(error, { next }) => {
if (error instanceOf AuthenticationError) {
return {
status: 401,
body: 'Unauthorized',
};
}
return error;
},
(error) => {
return {
status: 500,
body: 'Ups :('
};
}
]
To make it easier for you to handle different types of errors, simpleExpress provides you with handleError helper:
import { handleError } from 'simple-express-framework';
//...
errorHandlers: [
handleError(
AuthenticationError,
(error, { query, body, params, ... }) => ({
status: 401,
body: 'Unauthorized',
})
),
(error) => ({
status: 500,
body: 'Ups :('
}),
]
You can also pass an array of error - errorHandler pairs to handleError helper function
import { handleError } from 'simple-express-framework';
//...
errorHandlers: [
handleError([
[AuthenticationError, (error, { query, body, params, ... }) => ({
status: 401,
body: 'Unauthorized',
})],
[
(error) => ({
status: 500,
body: 'Ups :('
}),
]
])
]
import { handleError } from 'simple-express-framework';
//...
errorHandlers: handleError([
[AuthenticationError, (error, { query, body, params, ... }) => ({
status: 401,
body: 'Unauthorized',
})],
(error) => ({
status: 500,
body: 'Ups :('
}),
])
The simpleExpress supports different formats of routes (in first two formats, paths can be either strings or regular expressions):
simpleExpress({
routes: [
['/foo', {
get: [authenticate, () => ({ body: 'some data' })]
post: [authenticate, () => ({ status: 201 })]
}],
['/bar/:baz', {
get: ({ params }) => ({ body: `Got ${params.baz}` })
}]
]
});
simpleExpress({
routes: [
{
path: '/foo',
handlers: {
get: [authenticate, () => ({ body: 'some data' })],
post: [authenticate, () => ({ status: 201 })],
},
},
{
path: '/bar/:baz',
handlers: {
get: ({ params }) => ({ body: `Got ${params.baz}` }),
},
},
],
});
Warning: Object keys' order is preserved only for string and symbols keys, not for integers (integers, including in strings like "1", will always be before all other keys)!
simpleExpress({
routes: {
'/foo': {
get: [authenticate, () => ({ body: 'some data' })]
post: [authenticate, () => ({ status: 201 })]
},
'/bar/:baz': {
get: ({ params }) => ({ body: `Got ${params.baz}` }),
},
},
});
Because object keys can be used as route paths but also as method names (like get, post etc.) or as names like path, handlers and routes here is the list of reserved key names that can't be used as route paths when you use "Object of objects" format:
Please note that all of those can be used with slash at the beginning like /path or /post. Only exactly listed strings are reserved.
If you need to register a route with one of these strings as path, you can use one of the other route formats.
By default, JSON body parser, cors, cookie parser and helmet middlewares are configured. You can change their configuration or disable them.
config object consist of the following fields:
false, the cors middleware will not by applied.false the body parser middleware will not be applied.false the cookie parser middleware will not be applied.false the helmet middleware will not be applied.Note: Default middlewares are applied only if corresponding libraries are installed. body-parser is an express dependency so it will be installed with it.
Global middlewares can be added in middleware field. It is array of handlers (each handlers looks exactly like route handlers, with the same parameters).
simpleExpress({
port: 8080,
middleware,
...
})
If you need to use express middlewares directly, you can pass them to expressMiddleware array.
SimpleExpress allows you to pass two types of contexts to handlers: global and request. Both use asyncLocalStorage under the hood, meaning that most of the time you can access them in any place in your code without needed to pass them as parameters.
Global context is available in all handlers if you set the globalContext option in simpleExpress config. It has the same lifetime as the app. The object you provided in globalContext, is shallowly cloned once, and that one object is available (and mutable) in all handlers.
Request context is generated for each request and has the same lifetime as the request. requestContext option is a function, that receives all the fields that handlers receive except, obviously, both context objects and should return a plain JS object.
Both globalContext and requestContext can be accessed via
Each context object has the following shape:
Simple example
simpleExpress({
port: 8080,
globalContext: {
foo: 'bar',
},
routes: [
['/', {
get: ({ globalContext }) => {
return {
body: {
foo: globalContext.get('foo'), // bar
},
};
},
}],
],
})
Advanced example
import { simpleExpress, getGlobalContext } from 'simple-express-framework';
const handler = async () => {
const context = getGlobalContext();
const foo = context.get('foo');
context.set('foo', 'bam');
return foo;
}
let getGlobalContext;
simpleExpress({
port: 8080,
globalContext: {
foo: 'bar',
},
routes: [
['/bar', {
get: ({ globalContext }) => {
const foo = globalContext.get('foo');
globalContext.set('foo', 'baz');
return {
body: {
foo, // bar
},
};
},
}],
['/baz', async () => {
const foo = await handler();
return {
body: {
foo, // baz
},
};
}],
['/bam', async () => ({
body: {
foo: getGlobalContext().get('foo'), // bam
}
})],
],
}).then(({ port, getGlobalContext: globalContext }) => {
getGlobalContext = globalContext;
});
Simple example
simpleExpress({
port: 8080,
requestContext: ({ req }) => ({
foo: 'bar',
}),
routes: [
['/', {
get: () => ({
body: {
foo: getRequestContext().get('foo'),
},
}),
}],
],
})
Advanced example
import { simpleExpress, getRequestContext } from 'simple-express-framework';
const handler = async () => {
const context = getRequestContext();
const foo = context.get('foo');
context.set('foo', 'bam');
return foo;
}
let getRequestContext;
simpleExpress({
port: 8080,
requestContext: ({ req }) => ({
foo: 'bar',
}),
routes: [
['/bar', {
get: ({ requestContext }) => {
const foo = requestContext.get('foo');
requestContext.set('foo', 'baz');
return {
body: {
foo, // bar
},
};
},
}],
['/baz', async () => {
const foo = await handler();
return {
body: {
foo, // baz
},
};
}],
['/bam', async () => ({
body: {
foo: getRequestContext().get('foo'), // bam
}
})],
],
}).then(({ port, getRequestContext: requestContext }) => {
getRequestContext = requestContext;
});
You can modify the behaviour of simpleExpress with plugins. Each plugin is a factory, that receives simpleExpress config (all parameters passed to simpleExpress function) as parameter, and returns plugin object.
Plugin object is an object of one or more of the following functions:
Plugins' functions are triggered in the order they appear in the plugins array. In case of mapResponse, not all plugins are necessarily triggered (see details below).
Here is an example of plugin that adds cookies parsed by cookie-parser to handler params.
const cookiesPlugin = (simpleExpressConfig) => ({
getHandlerParams: params => ({
...params,
cookies: params.req.cookies,
}),
getErrorHandlerParams: params => ({
...params,
cookies: params.req.cookies,
}),
});
simpleExpress({
port: 8080,
plugins: [
cookiesPlugin,
],
...
});
And yet another plugin, this time, allowing you to serve file easily:
const serveFilePlugin = simpleExpressConfig => ({
mapResponse: (responseObject, { res }) => {
if (responseObject.file) {
res.sendFile(responseObject.file, err => {
if (err) {
return next(err)
}
})
return null;
}
return responseObject;
},
});
simpleExpress({
port: 8080,
plugins: [
serveFilePlugin,
],
...
});
These functions are triggered for all plugins, in order. Each plugin gets what the previous returned (default handler params are passed to the first plugin in chain) Arguments
Returns
Each plugin gets what previous returned (response object returned from handler is passed to the first plugin in chain). Chain can be broken if a plugin returns null or { type: 'none' } object. Also, no plugins are triggered, if handler returned null or { type: 'none' }. That way we prevent sending the response twice by two different plugins.
Arguments
Returns
SimpleExpress is written in typescript and most of the code is fully typed. One exception is the plugin system which does not correctly infer the types resulting in applying the plugins. Below are examples of how to handle that until the issue is fixed.
While most types are going to be correctly inferred, you can also pass the type parameters for additional route params and res.locals object.
import simpleExpress, { Routes } from 'simple-express-framework';
type AdditionalRouteParams = { foo: string };
type Locals = { bar: string };
export const router: Routes<AdditionalRouteParams, Locals> = [
['/', {
get: [
({ foo, locals }) => ({ body: `${foo} ${locals.bar}` }),
]
}],
];
// when passing routes with correct types, you can omit the generics in simpleExpress function
simpleExpress({
routes: router,
});
// but you can also pass them explicitly
simpleExpress<AdditionalRouteParams, Locals>({
routes: router,
});
This is the most difficult, and for now, the only way is to just use type assertion:
import { ResponseDefinition, Routes } from 'simple-express-framework';
const mapResponse = response => ({
...response,
body: response.alternativeBody,
});
const plugin = () => ({ mapResponse });
const routes: Routes = [
['/', {
get: [
() => ({ alternativeBody: 'works' } as ResponseDefinition),
]
}],
];
const { app } = await simpleExpress({ routes, plugins: [ plugin ] });
If you just add new parameters to handlers then you can use type parameter in route like this:
const getHandlerParams = routeParams => ({
...routeParams,
additionalParam: 'works',
});
const plugin = () => ({ getHandlerParams });
const routes: Routes<{ additionalParam: string }> = [
['/', {
get: [
({ additionalParam }) => ({ body: additionalParam }),
]
}],
];
const { app } = await simpleExpress({ routes, plugins: [ plugin ] });
In case you remove some parameters from handlers, unfortunately, your routes will not be type-safe:
const getHandlerParams = routeParams => ({
theOnlyParam: 'works',
});
const plugin = () => ({ getHandlerParams });
const routes: Routes<{ theOnlyParam: string }> = [
['/', {
get: [
// typescript allows you to use `req` param here although it won't be present at runtime
({ req, theOnlyParam }) => ({ body: theOnlyParam }),
]
}],
];
const { app } = await simpleExpress({ routes, plugins: [ plugin ] });
import simpleExpress from 'simple-express-framework';
simpleExpress({
port: 8080,
routes: [
['/', {
get: () => ({ body: 'Hello world!' }),
}],
],
})
import simpleExpress from 'simple-express-framework';
import connectDb from './connectDb';
import createUsersRepository from './usersRepository';
const db = connectDb();
const usersRepository = createUsersRepository(db);
simpleExpress({
port: 8080,
routeParams: { users: usersRepository },
routes: [
['/users', {
get: async ({ query: { search }, users }) => {
const allUsers = await users.getAll(search);
return {
body: allUsers,
};
},
post: async ({ body, users }) => {
const results = await users.create(body);
return {
status: 201,
body: results,
};
}],
['users/:id', {
get: async ({ params: { id }, users }) => {
const user = await users.getById(id);
if (user) {
return {
body: user,
};
}
return {
status: 404,
body: 'User not found',
};
},
put: async ({ params: { id }, body, users }) => {
const { id } = params;
const result = await users.updateById(id, body);
if (result) {
return {
status: 204,
};
}
return {
status: 404,
body: 'User not found',
};
},
}],
],
})
import simpleExpress from 'simple-express-framework';
import verifyToken from 'verifyToken';
simpleExpress({
port: 8080,
middleware: [
({ get, next }) => {
if (verifyToken(get('authentication'))) {
return next();
}
return {
status: 401,
body: 'Unauthorized',
};
},
],
routes: [
...
],
})
import simpleExpress from 'simple-express-framework';
import users from 'users';
import verifyToken from 'verifyToken';
simpleExpress({
port: 8080,
routes: [
...
['/users', {
get: [
({ get, next }) => {
if (verifyToken(get('authentication'))) {
return next();
}
return {
status: 401,
body: 'Unauthorized',
};
},
async ({ query }) => {
const { search } = query;
const allUsers = await users.getAll(search);
return {
body: allUsers,
};
},
]
}],
...
],
});
import simpleExpress from 'simple-express-framework';
import users from 'users';
import NotFoundError from 'errors/NotFoundError';
simpleExpress({
port: 8080,
routes: [
...
['/users/:id', {
get: async ({ params }) => {
const { id } = params;
const user = await users.getById(id);
if (user) {
return {
body: user,
};
}
return new NotFoundError('User not found');
},
}],
...
],
errorHandlers: [
(error, { next }) => {
if (error instanceof NotFoundError) {
return {
status: 404,
body: error.message,
};
}
return error;
},
(error) => (){
status: 500,
body: error.message || 'Unknown error',
}),
],
});
You can pass the error to next callback, return it or throw. In each case, error handlers will get the error.
const handler = ({ next }) => {
next(new Error('Error'));
};
const handler = () => {
return new Error('Error');
};
const handler = () => {
throw new Error('Error');
};
import simpleExpress from 'simple-express-framework';
simpleExpress({
port: 8080,
config: {
jsonBodyParser: false,
cors: false,
cookieParser: false,
},
routes: [
...
],
});
Cors, JSON body parser and cookie parser are configured by default
import simpleExpress from 'simple-express-framework';
import morgan from 'morgan';
simpleExpress({
port: 8080,
expressMiddleware: [
morgan('combined'),
],
routes: [
...
],
});
import simpleExpress from 'simple-express-framework';
simpleExpress({
port: 8080,
routes: [
['/foo', {
get: ({ res }) => {
res.write('<div>Hello foo</div>');
res.end();
},
}],
['/bar', {
get: ({ res }) => {
res.write('<div>Hello baz</div>');
res.end();
return null;
},
}],
['/baz', {
get: ({ res }) => {
res.write('<div>Hello baz</div>');
res.end();
return {
format: none,
};
},
}],
],
})
import simpleExpress, { wrapMiddleware } from 'simple-express';
const { check, validationResult } = require('express-validator');
const { app } = await simpleExpress({
port: 8080,
routes: [
['/user', {
post: [
wrapMiddleware([
check('username').isEmail(),
check('password').isLength({ min: 5 })
]),
({ req, next }) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
},
() => ({
body: 'works'
})
],
}],
],
});
This library uses debug logging utility.
To enable all logs set the following environment variable:
DEBUG=simpleExpress,simpleExpress:*
You can enable only some logs:
DEBUG=simpleExpress: General logs (like "App started on port", or "ERROR: port already in use")DEBUG=simpleExpress:request: Log all requests with response timeDEBUG=simpleExpress:stats: Simple express statistics, like registered routes, middlewares etc.DEBUG=simpleExpress:warning: Unimplemented features, deprecations and other warningsSee the demo app for tests examples.
npm iRunning tests
npm run test
Starting demo app
npm run demo
use method not being supportedsimpleExpressMiddlewares and middlewares options are deprecated, use middleware insteadexpressMiddlewares option is deprecated, use expressMiddleware insteadtype response fieldsendFAQs
> Simple micro framework based on Express, allowing you to prototype APIs blazingly quickly
We found that simple-express-framework demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.