Express
=========
|npm version| |Changelog|
.. code-block:: sh
$ npm install @feathersjs/express --save
The ``@feathersjs/express`` module contains
`Express `_ framework integrations for Feathers:
- The `Express framework bindings <#expressapp>`_ to make a Feathers
application Express compatible
- An Express based transport to expose services through a :ref:`api_express_rest`
- An :doc:`./errors`
.. code:: js
const express = require('@feathersjs/express');
..
.. important:: This page describes how to set up an Express
server and REST API. See the :doc:`./client/rest` how to use this server on the client.
.. important:: This chapter assumes that you are familiar with
`Express `_.
express(app)
------------
:doc:`./application` into a fully Express (4+) compatible
application that additionally to Feathers functionality also lets you
use the `Express API `_.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
// Create an app that is a Feathers AND Express application
const app = express(feathers());
.. note:: ``@feathersjs/express`` (``express``) also exposes the
standard `Express middleware `_:
- ``express.json`` - A JSON body parser
- ``express.urlencoded`` - A URL encoded form body parser
- ``express.static`` - To statically host files in a folder
- ``express.Router`` - Creates an Express router object
.. _express-1:
express()
---------
If no Feathers application is passed, ``express() -> app`` returns a
plain Express application just like a normal call to Express would.
app.use(path, service|mw|[mw])
------------------------------
:doc:`./services`.
an `Express middleware `_ or
an array of `Express middleware `_ on
the given path. If :doc:`./services` is passed it
will use Feathers registration mechanism, for a middleware function
Express.
.. code:: js
// Register a service
app.use('/todos', {
get(id) {
return Promise.resolve({ id });
}
});
// Register an Express middleware
app.use('/test', (req, res) => {
res.json({
message: 'Hello world from Express middleware'
});
});
// Register multiple Express middleware functions
app.use('/test', (req, res, next) => {
res.data = 'Step 1 worked';
next();
}, (req, res) => {
res.json({
message: 'Hello world from Express middleware ' + res.data
});
});
app.listen(port)
----------------
``app.listen(port) -> HttpServer`` will first call Express
`app.listen `_ and then
internally also call the :ref:`application_setupserver`.
.. code:: js
// Listen on port 3030
const server = app.listen(3030);
server.on('listening', () => console.log('Feathers application started'));
app.setup(server)
-----------------
``app.setup(server) -> app`` is usually called internally by
``app.listen`` but in the cases described below needs to be called
explicitly.
Sub-Apps
~~~~~~~~
When registering an application as a sub-app, ``app.setup(server)`` has
to be called to initialize the sub-apps services.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const api = express(feathers())
.configure(express.rest())
.use('/service', myService);
const mainApp = express().use('/api/v1', api);
const server = mainApp.listen(3030);
// Now call setup on the Feathers app with the server
api.setup(server);
..
.. tip:: We recommend avoiding complex sub-app setups because
websockets and Feathers built in authentication are not fully sub-app
aware at the moment.
HTTPS
~~~~~
HTTPS requires creating a separate server in which case
``app.setup(server)`` also has to be called explicitly.
.. code:: js
const fs = require('fs');
const https = require('https');
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
const server = https.createServer({
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
}, app).listen(443);
// Call app.setup to initialize all services and SocketIO
app.setup(server);
Virtual Hosts
~~~~~~~~~~~~~
The `vhost `_ Express middleware
can be used to run a Feathers application on a virtual host but again
requires ``app.setup(server)`` to be called explicitly.
.. code:: js
const vhost = require('vhost');
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
app.use('/todos', todoService);
const host = express().use(vhost('foo.com', app));
const server = host.listen(8080);
// Here we need to call app.setup because .listen on our virtal hosted
// app is never called
app.setup(server);
.. _api_express_rest:
express.rest()
--------------
``express.rest`` registers a Feathers transport mechanism that allows
you to expose and consume :doc:`./services` through a
`RESTful
API `_.
This means that you can call a service method through the ``GET``,
``POST``, ``PUT``, ``PATCH`` and ``DELETE`` `HTTP
methods `_:
============== =========== ===========
Service method HTTP method Path
============== =========== ===========
.find() GET /messages
.get() GET /messages/1
.create() POST /messages
.update() PUT /messages/1
.patch() PATCH /messages/1
.remove() DELETE /messages/1
============== =========== ===========
To expose services through a RESTful API we will have to configure
``express.rest`` and provide our own body parser middleware (usually the
standard `Express 4
body-parser `_) to make REST
``.create``, ``.update`` and ``.patch`` calls parse the data in the HTTP
body. If you would like to add other middleware *before* the REST
handler, call ``app.use(middleware)`` before registering any services.
.. tip:: The body-parser middleware has to be registered *before*
any service. Otherwise the service method will throw a
``No data provided`` or
``First parameter for 'create' must be an object`` error.
app.configure(express.rest())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Configures the transport provider with a standard formatter sending JSON
response via
`res.json `_.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
// Create an Express compatible Feathers application
const app = express(feathers());
// Turn on JSON parser for REST services
app.use(express.json())
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({ extended: true }));
// Set up REST transport
app.configure(express.rest())
app.configure(express.rest(formatter))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The default REST response formatter is a middleware that formats the
data retrieved by the service as JSON. If you would like to configure
your own ``formatter`` middleware pass a ``formatter(req, res)``
function. This middleware will have access to ``res.data`` which is the
data returned by the service.
`res.format `_ can be
used for content negotiation.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// Turn on JSON parser for REST services
app.use(express.json())
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({ extended: true }));
// Set up REST transport
app.configure(express.rest(function(req, res) {
// Format the message as text/plain
res.format({
'text/plain': function() {
res.end(`The Message is: "${res.data.text}"`);
}
});
}))
Custom service middleware
~~~~~~~~~~~~~~~~~~~~~~~~~
Custom Express middleware that only should run before or after a
specific service can be passed to ``app.use`` in the order it should
run:
.. code:: js
const todoService = {
get(id) {
return Promise.resolve({
id,
description: `You have to do ${id}!`
});
}
};
app.use('/todos', ensureAuthenticated, logRequest, todoService, updateData);
Middleware that runs after the service has the service call information
available as
- ``res.data`` - The data that will be sent
- :doc:`./hooks` context of the service
method call
For example ``updateData`` could look like this:
.. code:: js
function updateData(req, res, next) {
res.data.updateData = true;
next();
}
..
.. tip:: If you run ``res.send`` in a custom middleware after the
service and don’t call ``next``, other middleware (like the REST
formatter) will be skipped. This can be used to e.g. render different
views for certain service method calls.
params
~~~~~~
All middleware registered after the :doc:`./rest` will
have access to the ``req.feathers`` object to set properties on the
service method ``params``:
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const bodyParser = require('body-parser');
const app = express(feathers());
app.configure(express.rest())
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: true}))
.use(function(req, res, next) {
req.feathers.fromMiddleware = 'Hello world';
next();
});
app.use('/todos', {
get(id, params) {
console.log(params.provider); // -> 'rest'
console.log(params.fromMiddleware); // -> 'Hello world'
return Promise.resolve({
id, params,
description: `You have to do ${id}!`
});
}
});
app.listen(3030);
You can see the parameters set by running the example and visiting
``http://localhost:3030/todos/test``.
Avoid setting ``req.feathers = something`` directly since it may already
contain information that other Feathers plugins rely on. Adding
individual properties or using
``Object.assign(req.feathers, something)`` is the more reliable option.
.. important:: Since the order of Express middleware matters,
any middleware that sets service parameters has to be registered
*before* your services (in a generated application before
``app.configure(services)`` or in ``middleware/index.js``).
..
.. tip:: Although it may be convenient to set
``req.feathers.req = req;`` to have access to the request object in
the service, we recommend keeping your services as provider
independent as possible. There usually is a way to pre-process your
data in a middleware so that the service does not need to know about
the HTTP request or response.
params.query
~~~~~~~~~~~~
``params.query`` will contain the URL query parameters sent from the
client. For the REST transport the query string is parsed using the
`qs `_ module. For some query string
examples see the :doc:`./databases/querying`
chapter.
.. important:: Only ``params.query`` is passed between the server and
the client, other parts of ``params`` are not. This is for security
reasons so that a client can’t set things like ``params.user`` or the
database options. You can always map from ``params.query`` to other
:doc:`./hooks`.
For example:
.. code-block:: sh
GET /messages?read=true&$sort[createdAt]=-1
Will set ``params.query`` to
.. code:: js
{
"read": "true",
"$sort": { "createdAt": "-1" }
}
..
.. tip:: Since the URL is just a string, there will be **no type
conversion**. This can be done manually in a :doc:`./hooks` or
with the `query-types `_
Express middleware to convert Boolean and Numeric types.
.. note:: If an array in your request consists of more than 20 items,
the `qs `_ parser implicitly
`converts `_ it to an
object with indices as keys. To extend this limit, you can set a
custom query parser:
``app.set('query parser', str => qs.parse(str, {arrayLimit: 1000}))``
``params.provider``
~~~~~~~~~~~~~~~~~~~
For any :doc:`./services` made through REST
:doc:`./hooks`
this can for example be used to prevent external users from making a
service method call:
.. code:: js
app.service('users').hooks({
before: {
remove(context) {
// check for if(context.params.provider) to prevent any external call
if(context.params.provider === 'rest') {
throw new Error('You can not delete a user via REST');
}
}
}
});
params.route
~~~~~~~~~~~~
See the `routing section <#routing>`_.
express.notFound(options)
-------------------------
``express.notFound()`` returns middleware that returns a ``NotFound``
(404) :doc:`./errors`. It should be used as the last
middleware **before** the error handler. The following options are
available:
- ``verbose``: Set to ``true`` if the URL should be included in the
error message (default: ``false``)
.. code:: js
// Return errors that include the URL
app.use(express.notFound({ verbose: true });
app.use(errorHandler());
express.errorHandler()
----------------------
``expres.errorHandler`` is an `Express error
handler `_
middleware that formats any error response to a REST call as JSON (or
HTML if e.g. someone hits our API directly in the browser) and sets the
appropriate error code.
.. tip::
You can still use any other Express compatible `error middleware `_
with Feathers. In fact, the ``express.errors`` is just a slightly
customized one.
.. important::
Just as in Express, the error handler has to be registered *after* all middleware and services.
``app.use(express.errorHandler())``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Set up the error handler with the default configuration.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// before starting the app
app.use(express.errorHandler())
``app.use(express.errorHandler(options))``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
// Just like Express your error middleware needs to be
// set up last in your middleware chain.
app.use(express.errorHandler({
html: function(error, req, res, next) {
// render your error view with the error object
res.render('error', error);
}
}));
app.use(errorHandler({
html: {
404: 'path/to/notFound.html',
500: 'there/will/be/robots.html'
}
}));
..
.. tip:: If you want to have the response in json format be sure
to set the ``Accept`` header in your request to ``application/json``
otherwise the default error handler will return HTML.
The following options can be passed when creating a new localstorage
service:
- ``html`` (Function|Object) [optional] - A custom formatter function
or an object that contains the path to your custom html error pages.
- ``logger`` (Function|false) (default: ``console``) - Set a logger
object to log the error (it will be logger with
``logger.error(error)``
..
.. tip:: ``html`` can also be set to ``false`` to disable html
error pages altogether so that only JSON is returned.
Routing
-------
Express route placeholders in a service URL will be added to the
services ``params.route``.
.. important::
See the :ref:`faq_how-do-i-do-nested-or-custom-routes` for
more details on when and when not to use nested routes.
.. code:: js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const app = express(feathers());
app.configure(express.rest())
.use(function(req, res, next) {
req.feathers.fromMiddleware = 'Hello world';
next();
});
app.use('/users/:userId/messages', {
get(id, params) {
console.log(params.query); // -> ?query
console.log(params.provider); // -> 'rest'
console.log(params.fromMiddleware); // -> 'Hello world'
console.log(params.route.userId); // will be `1` for GET /users/1/messages
return Promise.resolve({
id,
params,
read: false,
text: `Feathers is great!`,
createdAt: new Date().getTime()
});
}
});
app.listen(3030);
.. |npm version| image:: https://img.shields.io/npm/v/@feathersjs/express.png?style=flat-square
:target: https://www.npmjs.com/package/@feathersjs/express
.. |Changelog| image:: https://img.shields.io/badge/changelog-.md-blue.png?style=flat-square
:target: https://github.com/feathersjs/feathers/blob/master/packages/express/CHANGELOG.md