Minimalist, monadic, typed http apps for http/express/hapi.
Install midori and add it to your package.json file:
npm install --save midoriTest 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.
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.');});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;});}),);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);});});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);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')),);You can connect midori to a number of other HTTP frameworks (like express, hapi).
import{send}from'midori';constapp=send('Hello world.');Install dependencies:
npm install --save midori-express expressCreate 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);Install dependencies:
npm install --save midori-hapi hapiCreate 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();Coming from another framework? Prefer to write your middleware handlers like you do in those other frameworks? Not a problem.
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);});