UNPKG

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