Expressa makes it easy to create basic APIs without custom code. It's easily extendable so you can add complex features as well. It includes a django-like admin interface for creating documents and managing permissions. Furthermore, the collection schema's themselves can be edited through the admin interface which makes adding new collections simple.
Those with experience in node and express can mix expressa with their own endpoints, since expressa is just a middleware. It allows adding event listeners which can stop requests and/or modify the results, so advanced functionality can be cleanly added.
The JSON schema standard format is used for describing the documents in a collection. This makes your schemas portable, so you can easily use them for another app and with other libraries.
Expressa lets you store different collections in different databases (thus taking advantage of each one's unique benefits). Documents can be stored in MongoDB, PostgreSQL, or just text files (useful for version control). Support for other JSON capable databases can be easily added.
Create a directory to hold your application, make that your working directory, and initialize npm
mkdir myapp
cd myapp
npm init
Install via npm
npm install expressa expressa-admin express
Create a file app.js
with the following code (or just copy the middle 3 lines into your existing express app)
var express = require('express');
var app = express();
var expressa = require('expressa');
app.use('/api', expressa);
app.use('/admin', expressa.admin());
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
Note: you can run the api server on a different host or prefix and then by passing the url like so expressa.admin({ apiurl:'/' })
node app.js
Navigate to http://localhost:3000/admin/ or to wherever the admin site is being served from and fill out the form.
GET /:collection>/
- get an array of all documents in a collectionGET /:collection>/:id
- get a specific documentGET /:collection>/?query={}
- get an array of documents matching the mongo queryGET /:collection>/?fieldname=value
- get an array of documents matching with the specified values. See node-mongo-querystring for details.GET /:collection>/schema
- get the collection schemaPOST /:collection/
- create a new document, the message body should be the JSON documentPUT /:collection/:id
- replace the document withid
. The message body should be the JSON document. If the _id in document is different (the old document_id
is deleted and a new one withid
is created.)POST /:collection/:id/update
- modify the document withid
using a mongo update query. The message body should be the update queryDELETE /:collection/:id
- delete the document
Only standard JSON (strings, numbers, booleans, null) is supported. Dates can be stored as strings using ISO 8601
POST /user/login
- expects JSON in the message body. e.g.{"email": "email@example.com", password: "<the password>"}
Returns the following
{
"success": true,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnVsbE5hbWUiOiJUaG9tYXMgSGFuc2VuIiwicGFzc3dvcmQiOiIkMmEkMTAkb0prdlBnTTlkR2FJRTIzaWFabGEvT0tjZC9PL3phSGFJOHFRUDBuZ2pPUVV1Ums3Vng2QkciLCJlbWFpbCI6InRoNDAxOUBnbWFpbC5jb20iLCJfX3YiOjAsImxpc3RpbmdzIjpbXSwiaWF0IjoxNDU2NDMwMjE5LCJleHAiOjE0NTY1MTY2MTl9._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM"
}
Or it will respond with a status code of 401 and a message explaining why they can't login.
{
"success": false,
"message": "Authentication failed. Wrong password."
}
The returned token must then be passed in as a header on future requests using the header x-access-token
GET /user/me
- returns the logged in user's object
Authentication using JSON Web Tokens
Obtain a token by sending a POST to /user/login
. This returns:
{
"id": "572d93513688657feede5877",
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI1NmNkNWM5NmY4MjA4N2I1MDQ0OTM3YjEiLCJ1bml2ZXJzaXR5IjoiQllVIiwiZnV._ijngdgwLU9AJnAjbySUgEFsR8hJCSw8PhH1AnyBHuM"
}
This token can then be passed as a queryparam (e.g. ?token=) or using the x-access-token
header.
Expressa lets you manage CRUD permissions for collections easily through the admin interface. Collections that are marked as having documents with owners have additional permissions for users interacting with their own documents.
Note: additional roles can be added through the interface
A meta property is added to objects which looks like the following.
"meta": {
"created": "2016-05-16T23:56:11.615Z",
"updated": "2016-05-16T23:56:28.262Z",
"owner": "56cb5df7f56ef0b92f7b984b"
},
Use expressa.addListener(eventTypes, priority, callback)
eventTypes
is a string or array of the event types listed below e.g. 'get' or ['put', 'post']
priority
is a number which determines the order of callback execution. Listeners with lower priority are executed first. If you don't care about order just use 0.
callback
is a function like the following:
function(req, collection, doc)
where
req
is the request
collection
is a string of the name of the collection acted upon
doc
is the relevant document.
Using these listeners you can control whether an action is allowed. Return true
to allow the action. Return false
(or an object with a custom message, as shown in the example below) to deny the action. Don't return anything or undefined
to let other listeners decide. If all listeners return undefined the action is allowed. Order is significant because it's the first defined return value that controls whether the action is allowed.
A promise can be returned so that asynchronous logic can be perfomed. In this case, it will wait for the promise to fulfill and use the resolved value.
get
- called once for each document being retrieved. Returning false in a request involving multiple documents (e.g. all or find) will simply remove that document from the list.post
- called before creating a new documentput
- called before changing a documentdelete
- called before deleting a document. Note: only the _id of the document is available in the callback. If the full document is needed you will need to load it yourself.
For example to prevent modifying old posts you could add the following listener:
expressa.addListener('put', 10, function(req, collection, doc) { if (collection == 'listing') { if (Date.now() - new Date(doc.meta.created) > (10006060*24)) { //older than a day return { code: 403, message: 'You cannot modify posts older than a day' } } } })
With these, the value returned from the listener is ignored.
changed
- called after a put or post has succeededdeleted
- called after a successful deletion
Each of the database implementations provides the following methods. You can access a collection's database using `api.db[collectionName]
- all - returns all documents
- find - returns array of documents matching the mongo query
- get - retrieve a document by id
- create - create a new document
- update - modify an existing document
- delete - delete a document by id
- init - called once during startup, useful to create/ensure collection exists
- MongoDB
- PostgreSQL (using jsonb)
- Text files (using json-file-store and mongo-query)
Other JSON capable databases can be added easily.
- JWT token expiration
- Support cookie based authentication as well
- JavaScript client library (something like dpd.js)
- File uploads