Skip to content

Repository files navigation

midori

Minimalist, monadic, typed http apps for http/express/hapi.

build statuscoveragelicenseversiondownloads

Usage

Install midori and add it to your package.json file:

npm install --save midori

Test out your first app:

import{get,send,listen}from'midori';constapp=get('/',send('Hello world.'));listen(app,8081,()=>{console.log('Example `midori` app started.');});
import{apply,query,url,send}from'midori';constapp=apply(query,url,(query,{pathname})=>{returnsend(`${pathname} - got ${query.foo}`);})

There are plenty of other examples available in the [./examples] folder.

Async

Many midori functions (including request and error) understand how to handle promises and async functions.

Using Promise:

import{send,request}from'midori';constgetData=()=>Promise.resolve(50);constapp=request(()=>{returngetData().then((result)=>{if(result>5){returnPromise.resolve(send('Yes.'));}returnPromise.resolve(send('No.'));});});

Using async / await:

import{request,send}from'midori';constgetData=()=>Promise.resolve(50);constapp=request(async()=>{constresult=awaitgetData();if(result>5){returnsend('Yes.');}returnsend('No.');});

Error Handling

Just as request provides a mechanism for dealing with request flow, error provides the same for handling errors.

import{request,error,compose,halt}from'midori';constapp=compose(request(()=>{// Can also `return Promise.reject();`thrownewError('Help!');}),error((err)=>{console.log(`I caught an error.`);returnhalt;}),);

If you need access to the request/response during error handling you can use those functions:

import{request,error,compose}from'midori';constapp=compose(request(()=>{// Can also `return Promise.reject();`thrownewError('Help!');}),error((err)=>{returnrequest((req)=>{console.log('There was an error at:',req.url);throwerr;});}),);

Testing

Testing Apps

midori includes a dedicated fetch() utility for testing apps:

import{response,next}from'midori';import{fetch}from'midori/test';constapp=response((res)=>{res.setHeader('Content-Type','test');returnnext;});it('should set the header',()=>{returnfetch(app,'/').then((res)=>{assert(res.headers['content-type']==='test');});});

But you can use a real HTTP server too:

import{response,listen,halt}from'midori';importfetchfrom'node-fetch';// Reference to HTTP server instance used in each test.letserver;leturl;constapp=response((res)=>{res.end('Hello world');returnhalt;});beforeEach(done=>{// Spin up a server and connect your app to it.server=listen(app,()=>{const{port}=server.address();url=`http://localhost:${port}`;done();});});afterEach(done=>{// Shut down the server after each test.server.close(done);server=null;url=null;});it('should return a result',()=>{returnfetch(url).then((res)=>{assert(res.statusCode===200);});});

Testing Selectors

You can use runSelector and getSelectorImplementation to test selectors in isolation.

import{createSelector}from'midori';import{runSelector,getSelectorImplementation}from'midori/test';importmySelectorAfrom'./mySelectorA';importmySelectorBfrom'./mySelectorB';jest.mock('./mySelectorA',()=>createSelector(jest.fn()));jest.mock('./mySelectorB',()=>createSelector(jest.fn()));constmySelectorC=createSelector(mySelectorA,mySelectorB,(a,b)=>a+b,);getSelectorImplementation(selectorA).mockImplementation(()=>1);getSelectorImplementation(selectorB).mockImplementation(()=>1);constresult=runSelector(mySelectorC);expect(result).toBe(2);

To mock request or other midori internal selectors you can:

import{request}from'midori';import{createMockRequest}from'midori/test';jest.mock('midori/request',()=>{returncreateSelector(jest.fn(()=>{returncreateMockRequest({url: '/foo',method: 'POST',});}));});

If you're not using jest or just want to mock values for a single test, then runSelector also provides a factory function with which you can use to setup your mocks.

import{createSelector}from'midori';import{runSelector}from'midori/test';importmySelectorAfrom'./mySelectorA';importmySelectorBfrom'./mySelectorB';constmySelectorC=createSelector(mySelectorA,mySelectorB,(a,b)=>a+b,);constresult=runSelector(mySelectorC,(inst)=>{inst.mockValue(mySelectorA,1);inst.mockValue(mySelectorB,1);});expect(result).toBe(2);

Advanced

Routing

The standard way of doing request dependent routing is by using match. Most frameworks allow you to only match against the request path and method, but midori makes no such compromises and you can use all kinds of predicates to determine the control flow of your application.

import{match,send,compose}from'midori';import{path,host}from'midori/match';constisFoo=path('/foo');// Match against URL pathconstisLocalhost=host(/localhost/);// Match against `Host` headerconstcreateApp=compose(match(isFoo,send('Hello from foo')),match(isLocalhost,send('You accessed from localhost')),);

You can also create match conjunctions using every (i.e. all predicates must be true for the match to succeed).

import{match,send,compose}from'midori';import{path,method,every}from'midori/match';// This is roughly how `get()` works internally.constisGetFoo=every(method('GET'),path('/foo'));constapp=compose(match(isGetFoo,send('Hello from foo')),);

You can also take action based on when the match fails:

import{match,send,compose}from'midori';import{path,host}from'midori/match';constisFoo=path('/foo');// Match against URL pathconstisLocalhost=host(/localhost/);// Match against `Host` headerconstapp=compose(match(isFoo,send('Hello from foo'),send('Hello not from foo')),);

Connectors

You can connect midori to a number of other HTTP frameworks (like express, hapi).

import{send}from'midori';constapp=send('Hello world.');

With express

Install dependencies:

npm install --save midori-express express

Create an express app and just use() your midori middleware as if it were express middleware:

importexpressfrom'express';importcreateMiddlewarefrom'midori-express';import{compose}from'midori';constexpressApp=express();expressApp.use(createMiddleware(app));expressApp.listen(8080);

With hapi

Install dependencies:

npm install --save midori-hapi hapi

Create a hapi app and register your midori middleware as an extension:

import{Server}from'hapi';importcreateExtfrom'midori-hapi';constserver=newServer();server.connection({port: 8080});server.ext(createExt(app));server.start();

Migration & Middleware Compatibility

Coming from another framework? Prefer to write your middleware handlers like you do in those other frameworks? Not a problem.

Express

The traditional callback style that express uses is compatible with midori. You can connect your express middleware as follows:

import{middleware}from'midori';constcreateMiddleware=middleware((req,res,next)=>{req.statusCode=201;next();});

Error handlers are also supported:

import{middleware}from'midori';constcreateMiddleware=middleware((err,req,res,next)=>{console.log('We got an error:',err);next(err);});

About

Minimalist, composable http middleware packs for http/express/hapi.

Topics

Resources

Stars

8 stars

Watchers

16 watching

Forks

Releases

Packages

Used by

Contributors

Languages