lapi

Light API framework for NodeJS

Usage no npm install needed!

<script type="module">
  import lapi from 'https://cdn.skypack.dev/lapi';
</script>

README

lapi

Light API framework for NodeJS

Build Status

Provide a lot of small classes to quickly build up a website with NodeJS

API Reference

Check out at here

Getting started

  • Get the framework ready
npm install --save lapi
  • Create a file, for instance, index.js with following contents:
var App = require('lapi').App
var app = new App()
app.start({
  'server.port': 8080
})

// Define routes here
var router = app.getRouter()
router.get('/hello/world').handler(function() {
  return {
    data: {
      say: "Hello World!"
    }
  }
})
  • Then, let's access to URL http://localhost:8080/hello/world
  • The response should be
{
  "data": {
    "say": "Hello World!"
  }
}

Extending App

  • Register event's listeners
const app = new App()
app.getEvents().on('http.server.ready', (args, next) => {
  console.log('Server is ready! at ' + args.host + ':' + args.port)
  next()
})
app.start({
  'server.port': 8080
})

Middleware

Middleware is a way to manipulate request before action actually runs

  • Add some attributes to request
var router = app.getRouter()
router.use('auth', (route, request) => {
  request.set('isAuthenticated', request.getHeader().has('X-TOKEN'))
})

router.get('/hello/world').handler(function() {
  return {
    data: {
      say: "Hello World!",
      auth: this.getRequest().get('isAuthenticated')
    }
  }
})
  • Send out a customized response
var JsonResponse = require('lapi').http.response.JsonResponse
app.use('auth', () => {
  return new JsonResponse({
    error: "Authentication is required"
  })
})

Controller

If you are a fan of MVC model, you would think that it would be nice to have a Controller instead of function

var Controller = require('lapi').http.Controller
class BlogController extends Controller {
  getItemAction() {
    return {
      data: {
        id: 1,
        title: "Some blog title"
      }
    }
  }
}

router.get('/blogs/{id}').require({id: /\d+/}).handler(new BlogController(), 'getItemAction')

Request

  • To set / get an attribute to request
router.get('/blogs/{id}').require({id: /\d+/}).handler(function() {
  this.getRequest().set('some-attribute', 'some-value')
  return {
    "data": this.getRequest().get('some-attribute')
  }
})
  • To get content of request's body
router.post('/blogs/{id}').require({id: /\d+/}).handler(function() {
  console.log(this.getRequest().getBody().getParsedContent())
})

Routing

  • Common methods is available
router.get(/* path */)
router.post(/* path */)
router.put(/* path */)
router.patch(/* path */)
router.delete(/* path */)
  • Path's requirements
router.get(/* path */).require({ /* requirements */})
  • Additional attributes
router.get(/* path */).with({ /* attributes */})
  • Middlewares
router.get(/* path */).middleware(['auth'])
  • Group of routes
router.group((router) => {
  router.get(/* path */) // path is /v1/path, middleware is auth
  router.get(/* other-path */).middleware(['new']) // path is /v1/other-path, middlewares are auth, new
}).prefix('/v1').middleware(['auth'])

Testing

It is always a must to test your application so as to make sure you are welcome for changes. Here are some steps to set up functional tests with lapi

  • Setup Application and API
const app = new App()
const api = new Api({
  host: 'localhost',
  port: 6767
})
  • Single request
app.use('auth', (route, request) => {
    return new JsonResponse({
      error: "Authentication is required"
    }, 400)
  })
app.start({'server.port': 6767}).then(() => {
    app.getRouter().get('/hello').middleware(['auth'])
    api.get('/hello').then(spec => {
      spec.hasHeaderKeyValue('content-type', 'application/json')
      spec.hasBodyParsedContent({
        error: "Authentication is required"
      })
    spec.hasStatusCode(400)
    done()
  }).catch(e => done(e))
})
  • Multiple requests
app.start({'server.port': 6767}).then(() => {
  app.getRouter().post('/users').handler(function() {
    return this.getRequest().getBody().getParsedContent().all()
  })
  api.post('/users', {
    name: "<name>",
    role: "<role>",
    company: "ABC"
  }).with({
    name: ["Tony", "Bill"],
    role: ["Boss", "Developer"]
  }).then(spec => {
    spec.hasPropertyKeyValue('name', spec.get('name'))
    spec.hasPropertyKeyValue('role', spec.get('role'))
    spec.hasPropertyKeyValue('company', "ABC")
  }).catch(e => done(e)).done(() => {done()})
})
  • Remember to stop application for each tests
afterEach(() => {
  if (app instanceof App) {
    app.stop()
  }
})

Extending Tests

You might need to check more in response in order to assure that response is exactly as expectation. You should extend the Spec class.

// my.spec.js
var Api = require('lapi').test.Api
var Spec = require('lapi').test.Spec
var assert = require('assert')

export default class MySpec extends Spec {
  constructor() {
    super()
  }

  hasSomeValue(value) {
    assert.equal(true, value)
  }
}
Api.Spec = MySpec // Mark it as default Spec

// my.test.js
app.start({'server.port': 6767}).then(() => {
    app.getRouter().get('/hello')
    api.get('/hello').then(spec => {
      spec.hasSomeValue('my-value')
      done()
  }).catch(e => done(e))
})