ohz

0.1.2 • Public • Published

Obvi

An HTTP server based on Observables

Usage

To see an example server running, install the repo ( yarn install ), and then run the server ( yarn dev ). This will start the server at the example.ts file.

Below is a stripped down version of using ohz with a vanilla server:

const { router, json, server: createServer } = require('ohz')
 
const server = createServer()
 
server
    .use(json())
    .use(router.route('/posts/:id', ctx => ({
        ...ctx,
        body: {
            query: ctx.request.query,
            param: ctx.request.params
        }
    })))
    .listen(5000, () => console.log('listening'))

Below is a vanilla JavaScript usage example with detailed comments:

// We are going to be using the map operator
// to make our middleware easier to write
const { of } = require('rxjs')
const { map } = require('rxjs/operators')
// We also want to import some middleware (router, json), 
// along with our server creator
const { router, json, server: createServer } = require('ohz')
 
// We create a server instance
// It has some basic generics that
// can get us up and running quickly
// so if you just want to see how it works
// you can do
//
// server = createServer()
// 
// without passing a configuration object
const server = createServer({
    // but if you want to get specific, we can
    // inject our own handlers into the system.
    // 
    // createContext is the first lego piece in our
    // system. It takes in a Subject<Context> and 
    // returns a function that takes in (http.ClientRequest, http.ServerResponse)
    // and emits an event into the Subject, with a value of Context.
    //
    // The `headers` value is not setting the _incoming_ headers
    // but setting the _outgoing_ headers.
    //
    // Below, we take in a generic subject and emit a generic context
    // but if you need to do more here, be sure that you include the
    // parsing of url and query or else router and other downstream
    // values will not behave as expected!
    createContext: sub => (request, response) => {
        const parsedUrl = url.parse(request.url)
 
        Object.assign(request, {
            url: parsedUrl, // { pathname: '/path/without/query' }
            query: parsequery(parsedUrl.search) // { query: args }
        })
 
        sub.next({
            request,
            response,
            headers: {},
            body: null
        })
    },
    // This is mostly a placeholder. As of now, the default
    // is the return an empty object. The idea is that you
    // can add values to the `server` that is returned
    // each time you call `server.use` or `server.remove`
    // or to override `use` and `remove` yourself.
    // 
    // I probably wouldn't use this but it's nice to have the
    // option to!
    createResult: () => ({})
})
 
 
server
    // Just like connect, we can call `use` to add middleware
    // to our server instance. They take a different signature
    // though! Instead of `(ctx, next) => void` or
    // `(req, res, next)` => void,
    // this middleware is of type
    // (ctx) => Observable<Context>.
    //
    // We have included some basic middlewares in order to 
    // get some basic boilerplate taken care of. `json` is
    // a module that takes in nothing and returns middleware
    // that handles incoming JSON data along with outgoing
    //
    // It does assume that the end-user is calling `send`
    // added by this middleware. If you change your subscribeFn
    // when you `listen`, you might get different results.
    .use(json())
    // We also have a way to add routes based on express-like urls
    // router.route takes in an express-like url and a handler. The
    // handler can either return a new Context value or return an 
    // Observable
    //
    // router.route will handle _all_ methods to that route.
    .use(router.route('/users', (ctx) => {
        // Here we return `of(...)` to show
        // that you can return Observables.
        // Remove `of` and just return a new
        // context value. It will work the same!
        return of({
            ...ctx,
            // We set the `body`
            // of the Context for the downstream
            // handlers to care about
            body: {
                data: [
                    {
                        _id: 1,
                        // We can also read from the incoming
                        // requests. This is assuming that the
                        // client has added a JSON body and it
                        // has a user value
                        name: ctx.request.body.user
                    }
                ]
            }
        })
    }))
    // If you want to only respond to get requests,
    // we can use router.get, which also takes a 
    // route and handler but will only respond
    // to get requests to that endpoint
    .use(router.get('/posts', (ctx) => ({
        ...ctx,
        body: {
            data: true
        }
    })))
    // Just like express, we can use named parameters
    // in our routes. This will be called when we have
    // the url /posts/1234.
    // 
    // As you can see in the body of this handler, we
    // have access to the params ( such as id ), along
    // with they query (  )
    .use(router.get('/posts/:id', (ctx) => ({
        ...ctx,
        body: {
            data: 'you did it!',
            meta: {
                params: ctx.request.params,
                query: ctx.request.query
            }
        }
    })))
    // We can also add generic Pipe-able functions
    // by using rxjs/operators. map(fn) is the same
    // as writing a function of Obs => Obs.map(fn)
    .use(map(({ body, headers, ...args }) => {
        if (!body) {
            return ({
                ...args,
                body: JSON.stringify({
                    error: {
                        message: 'Route not found'
                    }
                }),
                headers: {
                    ...headers,
                    code: 404,
                    'Content-Type': 'application/json'
                }
            })
        }
 
        return ({
            body,
            headers,
            ...args,
        })
    }))
    // Once we call listen, we can no longer add/remove middleware
    // 
    // We give listen a port, callback function, and subscribe function
    // 
    // port is the port to listen on
    // callback is the function to be called when the server is
    //      actually listening on that port
    // subscribe is a function that takes a Context and responds
    //      to the eventual value
    .listen(
        // Some random port will do, as long as it's not in use
        5000,
        // We just want to know when the server is ready!
        () => console.log('Listening at http://localhost:5000'),
        // This is the default subscription function.
        // 
        // We just take the context, try to use some `send`
        // method if the upstream set it ( like json() does )
        // or we write the headers, set the code, and write the 
        // body to the client, ending the conneciton.
        //
        // If you want to do something differently, you can change this
        // subscription function and handle the response however
        // you need to. If you just want to be able to set a string as
        // the value to be sent, don't include anything. If you want
        // to set JSON-able data, use the `json()` middleware as above.
        ({ request, response, body, headers = {} }) => {
            // the user has a custom handler
            if (response.send) {
                response.send(headers, body)
            } else {
                // default to string
                // Set headers
                response.writeHead(headers.code || 200, headers)
                // Assume middleware took care of the body
                response.end(body)
            }
        }
    )

Readme

Keywords

none

Package Sidebar

Install

npm i ohz

Weekly Downloads

9

Version

0.1.2

License

MIT

Unpacked Size

92 kB

Total Files

23

Last publish

Collaborators

  • beardedtim