UNPKG

3.72 kBPlain TextView Raw
1import { Request, Response, RequestHandler, Router } from 'express'
2import { MethodNotAllowed } from '@feathersjs/errors'
3import { createDebug } from '@feathersjs/commons'
4import { http } from '@feathersjs/transport-commons'
5import { createContext, defaultServiceMethods, getServiceOptions } from '@feathersjs/feathers'
6
7import { AuthenticationSettings, parseAuthentication } from './authentication'
8import { Application } from './declarations'
9
10const debug = createDebug('@feathersjs/express/rest')
11
12const toHandler = (
13 func: (req: Request, res: Response, next: () => void) => Promise<void>
14): RequestHandler => {
15 return (req, res, next) => func(req, res, next).catch((error) => next(error))
16}
17
18const serviceMiddleware = (): RequestHandler => {
19 return toHandler(async (req, res, next) => {
20 const { query, headers, path, body: data, method: httpMethod } = req
21 const methodOverride = req.headers[http.METHOD_HEADER] as string | undefined
22
23 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
24 const { service, params: { __id: id = null, ...route } = {} } = req.lookup!
25 const method = http.getServiceMethod(httpMethod, id, methodOverride)
26 const { methods } = getServiceOptions(service)
27
28 debug(`Found service for path ${path}, attempting to run '${method}' service method`)
29
30 if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) {
31 const error = new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`)
32 res.statusCode = error.code
33 throw error
34 }
35
36 const createArguments = http.argumentsFor[method as 'get'] || http.argumentsFor.default
37 const params = { query, headers, route, ...req.feathers }
38 const args = createArguments({ id, data, params })
39 const contextBase = createContext(service, method, { http: {} })
40 res.hook = contextBase
41
42 const context = await (service as any)[method](...args, contextBase)
43 res.hook = context
44
45 const response = http.getResponse(context)
46 res.statusCode = response.status
47 res.set(response.headers)
48 res.data = response.body
49
50 return next()
51 })
52}
53
54const servicesMiddleware = (): RequestHandler => {
55 return toHandler(async (req, res, next) => {
56 const app = req.app as any as Application
57 const lookup = app.lookup(req.path)
58
59 if (!lookup) {
60 return next()
61 }
62
63 req.lookup = lookup
64
65 const options = getServiceOptions(lookup.service)
66 const middleware = options.express.composed
67
68 return middleware(req, res, next)
69 })
70}
71
72export const formatter: RequestHandler = (_req, res, next) => {
73 if (res.data === undefined) {
74 return next()
75 }
76
77 res.format({
78 'application/json'() {
79 res.json(res.data)
80 }
81 })
82}
83
84export type RestOptions = {
85 formatter?: RequestHandler
86 authentication?: AuthenticationSettings
87}
88
89export const rest = (options?: RestOptions | RequestHandler) => {
90 options = typeof options === 'function' ? { formatter: options } : options || {}
91
92 const formatterMiddleware = options.formatter || formatter
93 const authenticationOptions = options.authentication
94
95 return (app: Application) => {
96 if (typeof app.route !== 'function') {
97 throw new Error('@feathersjs/express/rest needs an Express compatible app.')
98 }
99
100 app.use(parseAuthentication(authenticationOptions))
101 app.use(servicesMiddleware())
102
103 app.mixins.push((_service, _path, options) => {
104 const { express: { before = [], after = [] } = {} } = options
105
106 const middlewares = [].concat(before, serviceMiddleware(), after, formatterMiddleware)
107 const middleware = Router().use(middlewares)
108
109 options.express ||= {}
110 options.express.composed = middleware
111 })
112 }
113}