import { Request, Response, RequestHandler, Router } from 'express' import { MethodNotAllowed } from '@feathersjs/errors' import { createDebug } from '@feathersjs/commons' import { http } from '@feathersjs/transport-commons' import { createContext, defaultServiceMethods, getServiceOptions } from '@feathersjs/feathers' import { AuthenticationSettings, parseAuthentication } from './authentication' import { Application } from './declarations' const debug = createDebug('@feathersjs/express/rest') const toHandler = ( func: (req: Request, res: Response, next: () => void) => Promise ): RequestHandler => { return (req, res, next) => func(req, res, next).catch((error) => next(error)) } const serviceMiddleware = (): RequestHandler => { return toHandler(async (req, res, next) => { const { query, headers, path, body: data, method: httpMethod } = req const methodOverride = req.headers[http.METHOD_HEADER] as string | undefined // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const { service, params: { __id: id = null, ...route } = {} } = req.lookup! const method = http.getServiceMethod(httpMethod, id, methodOverride) const { methods } = getServiceOptions(service) debug(`Found service for path ${path}, attempting to run '${method}' service method`) if (!methods.includes(method) || defaultServiceMethods.includes(methodOverride)) { const error = new MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`) res.statusCode = error.code throw error } const createArguments = http.argumentsFor[method as 'get'] || http.argumentsFor.default const params = { query, headers, route, ...req.feathers } const args = createArguments({ id, data, params }) const contextBase = createContext(service, method, { http: {} }) res.hook = contextBase const context = await (service as any)[method](...args, contextBase) res.hook = context const response = http.getResponse(context) res.statusCode = response.status res.set(response.headers) res.data = response.body return next() }) } const servicesMiddleware = (): RequestHandler => { return toHandler(async (req, res, next) => { const app = req.app as any as Application const lookup = app.lookup(req.path) if (!lookup) { return next() } req.lookup = lookup const options = getServiceOptions(lookup.service) const middleware = options.express.composed return middleware(req, res, next) }) } export const formatter: RequestHandler = (_req, res, next) => { if (res.data === undefined) { return next() } res.format({ 'application/json'() { res.json(res.data) } }) } export type RestOptions = { formatter?: RequestHandler authentication?: AuthenticationSettings } export const rest = (options?: RestOptions | RequestHandler) => { options = typeof options === 'function' ? { formatter: options } : options || {} const formatterMiddleware = options.formatter || formatter const authenticationOptions = options.authentication return (app: Application) => { if (typeof app.route !== 'function') { throw new Error('@feathersjs/express/rest needs an Express compatible app.') } app.use(parseAuthentication(authenticationOptions)) app.use(servicesMiddleware()) app.mixins.push((_service, _path, options) => { const { express: { before = [], after = [] } = {} } = options const middlewares = [].concat(before, serviceMiddleware(), after, formatterMiddleware) const middleware = Router().use(middlewares) options.express ||= {} options.express.composed = middleware }) } }