Wajez API
REST API Development made easy.
Contents
What is that?
Wajez API is a library built on top of express
and mongoose
to make developing REST APIs as easy as possible while being able to control everything and override any behavior. I tried to build this library following the Functional Programming style using the beautiful library Sanctuary.
Installation
npm i --save wajez-api# or yarn add wajez-api
Basic Usage
Let's create a REST API for a simple blog, we will define our models using mongoose
:
demo/models/User.js
const mongoose = const Schema = mongoose const User = mongoose
demo/models/Post.js
const Post = mongoose
demo/models/Category.js
const Category = mongoose
Now we can write the application using express
and wajez-api
const express = const bodyParser = const api oneMany = const User Category Post = // create the appconst app = // body parser is required since the routes generated by wajez-api// assume that req.body already contains the parsed jsonappapp // define the models and relationsconst models = User Category Postconst relations = // add routes for resources User, Post and Categoryapp // add an error handlerapp
That's all, we now have a functional REST API with the following routes and features:
GET /users
: returns an array of users with format{id, name, type, email, password}
. The response headerContent-Total
will contain the total count of results. The query parametersoffset
,limit
andsort
can be used to sort by a field, specify the range of users to return. By defaultoffset = 0
andlimit = 100
. The query parameterwhere
can be used to filter results.GET /users/:id
: returns the single user having theid
ornull
if not found.GET /users/:id/posts
: returns the list of posts of a specific user. The query parametersoffset
,limit
andsort
are supported.POST /users
: adds and returns a new user with the data inreq.body
. Giving theposts
attribute as array of ids will update the corresponding posts to use the added user as theirwriter
. if some of the posts are missing or have already awriter
, an error is returned.PUT /users/:id
: modifies the user having theid
. Theposts
attribute can be given as array of ids or as an object of format:
add: ... // array of post ids to add remove: ... // array of post ids to remove
The writer
attribute of the involved posts will be updated accordingly.
-
DELETE /users/:id
: removes the user having theid
and sets thewriter
attribute of his posts tonull
. -
GET /posts
: similar toGET /users
, the format of a post is{id, category, writer, title, content}
.category
andwriter
contains the identifiers of the related resources. -
GET /posts/:id
: similar toGET /users/:id
. -
GET /posts/:id/writer
: returns the writer (user) of a specific post. -
GET /posts/:id/category
: returns the category of a specific post. -
POST /posts
: add and returns a new post. the attributeswriter
andcategory
can be given then the associations will be updated. -
PUT /posts/:id
: modifies a specific post. -
DELETE /posts/:id
: removes a specific post and removes its id from theposts
of the corresponding user and category. -
GET /categories
: similar toGET /users
. The format of a category is{id, parent, name}
-
GET /categories/:id
: returns a specific category ornull
if missing. -
GET /categories/:id/parent
: returns the parent of the category ornull
if missing. -
GET /categories/:id/children
: returns the list of subcategories of the category ornull
if missing. -
GET /categories/:id/posts
: returns the list of posts of the category. -
POST /categories
: adds and returns a new category. The attributesparent
,children
andposts
can be given then the associations will be updated. -
PUT /categories/:id
: modifies a specific category. -
DELETE /categories/:id
: removes a category, sets the parent of its children tonull
, sets the category of its posts tonull
and removes it from the children of its parent if any.
Now you may say:
Okay, that's cool. But the
passowrd
of the user should not be part of the response. How do I hide it? What if I want run a custom query or transform the data before sending it? is that possible?
Yes, all that is possible and easy to do. Check this demo application for a complet example.
API Reference
Let's start by listing the defined data types in this library.
Types
Express Types
-
Router
: an express Router. -
Request
: an express Request. -
Response
: an express Response. -
Middleware
: an express Middleware which is a function like(req, res, next) => {...}
. -
ErrorHandler
: an express ErrorHandler which is a function like(err, req, res, next) => {...}
.
Route Types
-
RouteMethod
: one ofget
,post
,put
anddelete
. -
RouteAction
: an object of format
step: Number middlewares: ArrayMiddelware
Route
: an object of format
uri: String method: RouteMethod actions: ArrayRouteAction
Query Types
-
Query
: one ofCreateQuery
,FindQuery
,CountQuery
,UpdateQuery
, andRemoveQuery
. -
CreateQuery
: an object of format
type: 'create' data: Object relations: ArrayRelation
FindQuery
: an object of format
type: 'find' conditions: Object projection: String | null options: Object populate: Array path: String match: Object select: String | null options: Object
CountQuery
: an object of format
type: 'count' conditions: Object
UpdateQuery
: an object of format
type: 'update' conditions: Object data: Object relations: ArrayRelation
RemoveQuery
: an object of format
type: 'remove' conditions: Object relations: ArrayRelation
Others
Relation
: represents the relation between two models. in has the following format
type: 'one-one' | 'one-many' | 'many-one' | 'many-many' source: name: String // the source model name field: String | null // the source model field, if any. target: name: String // the target model name field: String | null // the target model field, if any.
Relations
oneOne
Relation
Creates a One to One relation between two models.
Example
const User = mongoose const Account = mongoose const relation =
Note: The target field name can be null
if the field is absent.
oneMany
Relation
Creates a One to Many relation between two models.
Example
const User = mongoose const Post = mongoose const relation =
Note: The target or the source field name can be null
if the field is absent. But not both!
manyOne
Relation
Creates a Many to One relation between two models.
===
manyMany
Relation
Creates a Many to Many relation between two models.
Example
const Post = mongoose const Tag = mongoose const relation =
Note: The target or the source field name can be null
if the field is absent. But not both!
Routes
get, post, put, remove
String uri ArrayRouteAction actions Route
Create a GET
, POST
, PUT
and DELETE
route respectively, with the given uri
and actions
.
Example
const router action get = const app = // a hello world routeconst hello = app// GET /hello will print "Hello World!"
extend
(Route r, {method, uri, actions}) => Route
Extends the route r
by overriding its method
or uri
if given, and adding actions
if given.
Example
const router action get extend = const app = const hello = const yo = console// {// method: 'get',// uri: '/hello',// actions: [// action(1, [// (req, res, next) => res.send('Hello World!')// ]),// action(0, [// (req, res, next) => res.send('Yo!')// ])// ]// } app// GET /hello will print "Yo!"
Note: actions of a route are sorted by their steps, that's why in the previous example, even if the yo
route contains two actions, the one with step 0
is executed first.
login
String secret Model model ArrayString fields uri actions = {} Route
Creates a POST
route to /login
(overwritten by uri
if given) that performs an authentication as follows:
-
Checks that
req.body
has the specifiedfields
. if a field is missing then aWrongCredentialsError
error is thrown. -
Checks that a record of the given
model
with fields values is present on database. if not aWrongCredentialsError
is thrown. -
returns a response of format
{token: '....'}
containing a JSON Web Token to be used for authentication.
list
Route
Constructs a route that returns a list of the given model, then merges the converter
if given with the default converter, and extends the route using the uri
and actions
if any. By default:
GET /plural-of-model-name
- The default converter returns only fields of types (
ObjectId
,String
,Number
,Boolean
,Buffer
,Date
). It ignores allObject
andArray
fields. - The offset parameter is set from query parameter
offset
, same forlimit
,sort
, andwhere
parameters. - The
where
parameter is parsed as JSON and used as query conditions if given. - Default values for offset and limit are
0
and100
respectively. No sort is defined by default. - The response header
Content-Total
will contain the total count of items matching thewhere
conditions.
show
Route
Constructs a route that returns a specific document of the given model by id, then merges the converter
if given with the default converter, and extends the route using the uri
and actions
if any. By default:
GET /plural-of-model-name/:id
.- The default converter is the same as
list
.
add
Route
Constructs a route that adds new document of the given model, then merges the converter
if given with the default converter, and extends the route using the uri
and actions
if any. By default:
POST /plural-of-model-name
.- The default converter is the same as
list
. - Handles
relations
by synchronizing the corresponding documents from other models if needed.
edit
Route
Constructs a route that modifies a document of the given model, then merges the converter
if given with the default converter, and extends the route using the uri
and actions
if any. By default:
PUT /plural-of-model-name/:id
.- The default converter is the same as
list
. - Handles
relations
by synchronizing the corresponding documents from other models if needed.
destroy
Route
Constructs a route that removes a document of the given model and extends the route using the uri
and actions
if any. By default:
DELETE /plural-of-model-name
.- Handles
relations
by synchronizing the corresponding documents from other models if needed.
showRelated
Route
Constructs a route that shows related target
s for a specific source
of the given relation. Then extends it with the given uri
, converter
and actions
. By default:
- if
one-one
ormany-one
relation then the route is similar toshow
with a uri/sources/:id/target
. - if
one-many
ormany-many
relation then the route is similar tolist
with a uri/sources/:id/targets
.
resource
ArrayRoute
This is a shortcut to generate all resource routes for a model at once, while being able to configure each route. The relations
and defaults
configurations will be passed to all routes after merging them with the corresponding configuration of the route. The fields
specify the configuration of the route showRelated
for each field that corresponds to a relation. Check the demo application for examples.
Actions
action
Number step ArrayMiddleware | Middleware middlewares RouteAction
Creates a route action, which is an object that wraps a sequence of middlewares
to be executed in a specific step
.
Default steps values are
onStart: 1
onReadParams: 2
beforeQuery: 3
onQuery: 4
beforeRun: 5
onRun: 6
beforeConvert: 7
onConvert: 8
beforeSend: 9
onSend: 10
afterSend: 11
inTheEnd: 12
The functions onStart
, onReadParams
, beforeQuery
, onQuery
, beforeRun
, onRun
, beforeConvert
, onConvert
, beforeSend
, onSend
, afterSend
, and inTheEnd
are defined to create actions for the corresponding step; they take a middelware or array of middlewares as argument.
Middlewares
auth
Middleware
Creates an authentication middleware. This uses express-jwt
internally. The opts
are passed to express-jwt
then req.user
is set to the document of model
. Check the demo application for usage example.
setQuery
PromiseQuery queryGetter Middleware
Takes a function queryGetter
as argument and returns a middleware. The function queryGetter
should take the request object as parameter and return a promise containing the Query
. When the resulting middleware is executed, it will run queryGetter
and save the returned query so that we can run it later. See runQuery and getQuery.
getQuery
Query | null
Reads the query from the request object or returns null
if no query is set.
runQuery
Middleware
Takes a model and returns a middleware which when executed will run the query (set beforehand) with the given model
and set the resulting data in the request object. This data can be read and transformed before being sent in the response. See getData, setData, and convertData.
getData
Object
Reads the data from the request object or returns null
if no data is set.
setData
PromiseObject dataGetter Middleware
Similar to setQuery. The resulting middleware will provide the request to dataGetter
and set resulting data.
convertData
PromiseAny converterGetter Middleware
The resulting middleware will call converterGetter
with the request, it should return a promise containing a Converter. Then the converter is used to convert the data.
getRoute
String
Gets the running route from the request, the value is one of login
, list
, add
, show
, edit
, destroy
,show-one-related
, and show-many-related
.
getModel
String
Gets the running route model name from the request.
getRelated
String
Gets the running route related model name from the request. This only makes sense on routes show-one-related
and show-many-related
; the value is null
in other cases.
setOffset
Middleware
Returns a middleware that will set the offset
value from the query parameter with name queryParamName
or use the defaultValue
if missing.
getOffset
Number
Reads the offset
from the request object.
setLimit, getLimit, setSort, getSort
Similar to setOffset
and getOffset
.
Routers
router
ArrayRoute routes Router
makes an express router from an array of routes.
api
ArrayModel models ArrayRelation relations _all Model1 Model2 ... Router
Returns an express router containing all resource
routes of all given models. Passes relations
and _all
config, if given, to all models, merged with the corresponding config if exists.
Development Notes
-
1.6.0: When updating an array field
items
, the fielditemsLength
is auto-updated if present. -
1.5.0: The default resource converter now returns fields of type
ObjectId
. -
1.4.0: The response of
list
andshowRelated
contains now a headerContent-Total
equal to the total count of items; useful for pagination. -
1.3.0: The query parameter
where
is now used to filter results onlist
andshow-many-related
routes. -
1.2.0:
req.body
is now used to filter results onlist
andshow-many-related
routes. -
1.0.0: A complete new version is finally out!