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