mirror of
https://github.com/feathersjs/feathers.git
synced 2025-12-08 19:46:22 +00:00
571 lines
19 KiB
Markdown
571 lines
19 KiB
Markdown
---
|
|
outline: deep
|
|
---
|
|
|
|
# Express
|
|
|
|
<Badges>
|
|
|
|
[](https://www.npmjs.com/package/@feathersjs/express)
|
|
[](https://github.com/feathersjs/feathers/blob/dove/packages/express/CHANGELOG.md)
|
|
|
|
</Badges>
|
|
|
|
```
|
|
npm install @feathersjs/express --save
|
|
```
|
|
|
|
The `@feathersjs/express` module contains [Express](http://expressjs.com/) 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 [REST API](#expressrest)
|
|
- An [Express error handler](#expresserrorhandler) for [Feathers errors](./errors.md)
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
```
|
|
|
|
<BlockQuote type="warning" label="Important">
|
|
|
|
This chapter assumes that you are familiar with [Express](http://expressjs.com/en/guide/routing.html) and describes how to set up an Express server and REST API. See the [REST client chapter](./client/rest.md) how to use this server on the client.
|
|
|
|
</BlockQuote>
|
|
|
|
## express(app)
|
|
|
|
`express(app) -> app` is a function that turns a [Feathers application](./application.md) into a fully Express (4+) compatible application that additionally to Feathers functionality also lets you use the [Express API](http://expressjs.com/en/4x/api.html).
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
```
|
|
|
|
Note that `@feathersjs/express` also exposes the standard [Express middleware](http://expressjs.com/en/4x/api.html#express):
|
|
|
|
- `json` - A JSON body parser
|
|
- `urlencoded` - A URL encoded form body parser
|
|
- `serveStatic` - To statically host files in a folder
|
|
- `Router` - Creates an Express router object
|
|
|
|
```ts
|
|
import { json, urlencoded, serveStatic, Router } from '@feathersjs/express'
|
|
```
|
|
|
|
## express(app, expressApp)
|
|
|
|
`express(app, expressApp) -> app` allows to extend an existing Express application with the Feathers application `app`.
|
|
|
|
## 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\])
|
|
|
|
`app.use(path, service|mw|[mw]) -> app` registers either a [service object](./services.md), an [Express middleware](http://expressjs.com/en/guide/writing-middleware.html) or an array of [Express middleware](http://expressjs.com/en/guide/writing-middleware.html) on the given path. If [a service object](./services.md) is passed it will use Feathers registration mechanism, for a middleware function Express.
|
|
|
|
```ts
|
|
// Register a service
|
|
app.use('todos', {
|
|
async get(id) {
|
|
return { 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) -> Promise<HttpServer>` will first call Express [app.listen](http://expressjs.com/en/4x/api.html#app.listen) and then internally also call the [Feathers app.setup(server)](./application.md#setupserver).
|
|
|
|
```ts
|
|
// Listen on port 3030
|
|
const server = await app.listen(3030)
|
|
```
|
|
|
|
## 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.
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@feathersjs/express'
|
|
|
|
const api = feathers()
|
|
|
|
api.use('service', myService)
|
|
|
|
const mainApp = express(feathers()).use('/api/v1', api)
|
|
|
|
const server = await mainApp.listen(3030)
|
|
|
|
// Now call setup on the Feathers app with the server
|
|
await api.setup(server)
|
|
```
|
|
|
|
### HTTPS
|
|
|
|
HTTPS requires creating a separate server in which case `app.setup(server)` also has to be called explicitly. In a generated application `src/index.js` should look like this:
|
|
|
|
```ts
|
|
import https from 'https'
|
|
import { app } from './app'
|
|
|
|
const port = app.get('port');
|
|
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)
|
|
|
|
server.on('listening', () =>
|
|
logger.info('Feathers application started')
|
|
)
|
|
```
|
|
|
|
### Virtual Hosts
|
|
|
|
The [vhost](https://github.com/expressjs/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.
|
|
|
|
```ts
|
|
import vhost from 'vhost'
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
|
|
app.use('/todos', todoService)
|
|
|
|
const host = express().use(vhost('foo.com', app))
|
|
const server = await host.listen(8080)
|
|
|
|
// Here we need to call app.setup because .listen on our virtual hosted
|
|
// app is never called
|
|
app.setup(server)
|
|
```
|
|
|
|
## rest()
|
|
|
|
Registers a Feathers transport mechanism that allows you to expose and consume [services](./services.md) through a [RESTful API](https://en.wikipedia.org/wiki/Representational_state_transfer). This means that you can call a service method through the `GET`, `POST`, `PUT`, `PATCH` and `DELETE` [HTTP methods](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol):
|
|
|
|
| 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 |
|
|
|
|
|
|
### app.configure(rest())
|
|
|
|
Configures the transport provider with a standard formatter sending JSON response via [res.json](http://expressjs.com/en/4x/api.html#res.json).
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express, { json, urlencoded, rest } from '@feathersjs/express'
|
|
|
|
// Create an Express compatible Feathers application
|
|
const app = express(feathers())
|
|
|
|
// Turn on JSON parser for REST services
|
|
app.use(json())
|
|
// Turn on URL-encoded parser for REST services
|
|
app.use(urlencoded({ extended: true }))
|
|
// Set up REST transport
|
|
app.configure(rest())
|
|
```
|
|
|
|
<BlockQuote type="warning" label="Important">
|
|
|
|
The `json` and `urlencoded` body parser middleware has to be registered _before_ any service. Otherwise the service method throw an error that data is `undefined`.
|
|
|
|
</BlockQuote>
|
|
|
|
### app.configure(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](http://expressjs.com/en/4x/api.html#res.format) can be used for content negotiation.
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express, { json, urlencoded, rest } from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
|
|
// Turn on JSON parser for REST services
|
|
app.use(json())
|
|
// Turn on URL-encoded parser for REST services
|
|
app.use(urlencoded({ extended: true }))
|
|
// Set up REST transport
|
|
app.configure(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:
|
|
|
|
```ts
|
|
const todoService = {
|
|
async get(id: Id) {
|
|
return {
|
|
id,
|
|
description: `You have to do ${id}!`
|
|
}
|
|
}
|
|
}
|
|
|
|
app.use('todos', logRequest, todoService, updateData);
|
|
```
|
|
|
|
<BlockQuote type="danger">
|
|
|
|
Custom middleware will only run for REST requests and not when used with other transports (like Socket.io). If possible try to avoid custom middleware and use [hooks](hooks.md) or customized services instead which will work for all transports.
|
|
|
|
</BlockQuote>
|
|
|
|
Middleware that runs after the service has the service call information available as
|
|
|
|
- `res.data` - The data that will be sent
|
|
- `res.hook` - The [hook](./hooks.md) context of the service method call
|
|
|
|
For example `updateData` could look like this:
|
|
|
|
```js
|
|
function updateData(req, res, next) {
|
|
res.data.updateData = true
|
|
next()
|
|
}
|
|
```
|
|
|
|
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, for example to export a file as CSV:
|
|
|
|
```ts
|
|
import json2csv from 'json2csv'
|
|
|
|
const fields = [ 'done', 'description' ]
|
|
|
|
app.use('/todos', todoService, function(req, res) {
|
|
const result = res.data
|
|
const data = result.data // will be either `result` as an array or `data` if it is paginated
|
|
const csv = json2csv({ data, fields })
|
|
|
|
res.type('csv')
|
|
res.end(csv)
|
|
})
|
|
```
|
|
|
|
### params
|
|
|
|
All middleware registered after the [REST transport](#express-rest) will have access to the `req.feathers` object to set properties on the service method `params`:
|
|
|
|
```ts
|
|
import { feathers, type Id, type Params } from '@feathersjs/feathers'
|
|
import express, { json, urlencoded, rest } from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
|
|
app.configure(rest())
|
|
.use(json())
|
|
.use(urlencoded({extended: true}))
|
|
.use(function(req, res, next) {
|
|
req.feathers.fromMiddleware = 'Hello world'
|
|
next()
|
|
})
|
|
|
|
app.use('todos', {
|
|
async get(id: Id, params: Params) {
|
|
console.log(params.provider) // -> 'rest'
|
|
console.log(params.fromMiddleware) // -> 'Hello world'
|
|
|
|
return {
|
|
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 `{ ...req.feathers, something }` is the more reliable option.
|
|
|
|
<BlockQuote type="warning">
|
|
|
|
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`).
|
|
|
|
</BlockQuote>
|
|
|
|
<BlockQuote type="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.
|
|
|
|
</BlockQuote>
|
|
|
|
### 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](https://github.com/ljharb/qs) module. For some query string examples see the [database querying](./databases/querying.md) chapter.
|
|
|
|
<BlockQuote type="warning">
|
|
|
|
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 `params` properties in a [hook](./hooks.md).
|
|
|
|
</BlockQuote>
|
|
|
|
For example:
|
|
|
|
```
|
|
GET /messages?read=true&$sort[createdAt]=-1
|
|
```
|
|
|
|
Will set `params.query` to
|
|
|
|
```json
|
|
{
|
|
"read": "true",
|
|
"$sort": { "createdAt": "-1" }
|
|
}
|
|
```
|
|
|
|
<BlockQuote type="tip">
|
|
|
|
Note that the URL is a string so type conversion may be necessary. This is usually done with [query schemas and resolvers](./schema/index.md).
|
|
|
|
</BlockQuote>
|
|
|
|
<BlockQuote type="danger">
|
|
|
|
If an array in your request consists of more than 20 items, the [qs](https://www.npmjs.com/package/qs) parser implicitly [converts](https://github.com/ljharb/qs#parsing-arrays) 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}))`
|
|
|
|
</BlockQuote>
|
|
|
|
### params.provider
|
|
|
|
For any [service method call](./services.md) made through REST `params.provider` will be set to `rest`. In a [hook](./hooks.md) this can for example be used to prevent external users from making a service method call:
|
|
|
|
```ts
|
|
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).
|
|
|
|
## notFound(options)
|
|
|
|
`express.notFound()` returns middleware that returns a `NotFound` (404) [Feathers error](./errors.md). 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`)
|
|
|
|
```ts
|
|
import { notFound, errorHandler } from '@feathersjs/express'
|
|
|
|
// Return errors that include the URL
|
|
app.use(notFound({ verbose: true }))
|
|
app.use(errorHandler())
|
|
```
|
|
|
|
## errorHandler()
|
|
|
|
`errorHandler` is an [Express error handler](https://expressjs.com/en/guide/error-handling.html) 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.
|
|
|
|
<BlockQuote type="tip">
|
|
|
|
You can still use any other Express compatible [error middleware](http://expressjs.com/en/guide/error-handling.html) with Feathers.
|
|
|
|
</BlockQuote>
|
|
|
|
<BlockQuote type="warning" label="Important">
|
|
|
|
Just as in Express, the error handler has to be registered *after* all middleware and services.
|
|
|
|
</BlockQuote>
|
|
|
|
### app.use(errorHandler())
|
|
|
|
Set up the error handler with the default configuration.
|
|
|
|
```js
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
|
|
// before starting the app
|
|
app.use(express.errorHandler())
|
|
```
|
|
|
|
### app.use(errorHandler(options))
|
|
|
|
```js
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express from '@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'
|
|
}
|
|
}))
|
|
```
|
|
|
|
<BlockQuote type="warning" label="important">
|
|
|
|
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.
|
|
|
|
</BlockQuote>
|
|
|
|
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. Can also be set to `false` to disable html error pages altogether so that only JSON is returned.
|
|
- `logger` (Function|false) (default: `console`) - Set a logger object to log the error (it will be logger with `logger.error(error)`
|
|
|
|
|
|
## authenticate()
|
|
|
|
`express.authenticate(...strategies)` allows to protect an Express middleware with an [authentication service](./authentication/service.md) that has [strategies](./authentication/strategy.md) registered that can parse HTTP headers. It will set the authentication information on the `req` object (e.g. `req.user`). The following example protects the `/hello` endpoint with the JWT strategy (so the `Authorization: Bearer <JWT>` header needs to be set) and uses the user email to render the message:
|
|
|
|
```ts
|
|
import { authenticate } from '@feathersjs/express'
|
|
|
|
app.use('/hello', authenticate('jwt'), (req, res) => {
|
|
const { user } = req
|
|
|
|
res.render(`Hello ${user.email}`)
|
|
})
|
|
|
|
// When using with the non-default authentication service
|
|
app.use('/hello', authenticate({
|
|
service: 'v2/auth',
|
|
strategies: [ 'jwt', 'api-key' ]
|
|
}), (req, res) => {
|
|
const { user } = req
|
|
|
|
res.render(`Hello ${user.email}`)
|
|
})
|
|
```
|
|
|
|
When clicking a normal link, web browsers do _not_ send the appropriate header. In order to initate an authenticated request to a middleware from a browser link, there are two options. One is using a session which is described in the [Server Side rendering guide](../cookbook/express/view-engine.md), another is to add the JWT access token to the query string and mapping it to an authentication request:
|
|
|
|
```ts
|
|
import { authenticate } from '@feathersjs/express'
|
|
const setQueryAuthentication = (req, res, next) => {
|
|
const { access_token } = req.query;
|
|
|
|
if (access_token) {
|
|
req.authentication = {
|
|
strategy: 'jwt',
|
|
accessToken: access_token
|
|
}
|
|
}
|
|
|
|
next();
|
|
}
|
|
|
|
// Request this with `hello?access_token=<your jwt>`
|
|
app.use('/hello', setQueryAuthentication, authenticate('jwt'), (req, res) => {
|
|
const { user } = req
|
|
|
|
res.render(`Hello ${user.email}`)
|
|
})
|
|
```
|
|
|
|
How to get the access token from the authentication client is described in the [authentication client documentation](../api/authentication/client.md#app-get-authentication).
|
|
|
|
<BlockQuote type="warning">
|
|
|
|
When using HTTPS URLs are safely encrypted but when using this method you have to make sure that access tokens are not logged through any of your logging mechanisms.
|
|
|
|
</BlockQuote>
|
|
|
|
## Routing
|
|
|
|
Express route placeholders in a service URL will be added to the services `params.route`. See the [FAQ entry on nested routes](../help/faq.md#how-do-i-do-nested-or-custom-routes) for more details on when and when not to use nested routes.
|
|
|
|
```ts
|
|
import { feathers } from '@feathersjs/feathers'
|
|
import express, { rest } from '@feathersjs/express'
|
|
|
|
const app = express(feathers())
|
|
|
|
app.configure(rest())
|
|
.use(function(req, res, next) {
|
|
req.feathers.fromMiddleware = 'Hello world'
|
|
next()
|
|
})
|
|
|
|
app.use('users/:userId/messages', {
|
|
async 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 {
|
|
id,
|
|
params,
|
|
read: false,
|
|
text: `Feathers is great!`,
|
|
createdAt: new Date().getTime()
|
|
}
|
|
}
|
|
})
|
|
|
|
app.listen(3030)
|
|
```
|