UNPKG

12.2 kBPlain TextView Raw
1// Copyright IBM Corp. and LoopBack contributors 2018,2020. All Rights Reserved.
2// Node module: @loopback/rest
3// This file is licensed under the MIT License.
4// License text available at https://opensource.org/licenses/MIT
5
6import {
7 Application,
8 ApplicationConfig,
9 Binding,
10 BindingAddress,
11 Constructor,
12 Context,
13 Provider,
14 Server,
15} from '@loopback/core';
16import {
17 ExpressMiddlewareFactory,
18 ExpressRequestHandler,
19 Middleware,
20 MiddlewareBindingOptions,
21} from '@loopback/express';
22import {OpenApiSpec, OperationObject} from '@loopback/openapi-v3';
23import {PathParams} from 'express-serve-static-core';
24import {ServeStaticOptions} from 'serve-static';
25import {format} from 'util';
26import {BodyParser} from './body-parsers';
27import {RestBindings} from './keys';
28import {RestComponent} from './rest.component';
29import {HttpRequestListener, HttpServerLike, RestServer} from './rest.server';
30import {ControllerClass, ControllerFactory, RouteEntry} from './router';
31import {RouterSpec} from './router/router-spec';
32import {SequenceFunction, SequenceHandler} from './sequence';
33
34export const ERR_NO_MULTI_SERVER = format(
35 'RestApplication does not support multiple servers!',
36 'To create your own server bindings, please extend the Application class.',
37);
38
39// To help cut down on verbosity!
40export const SequenceActions = RestBindings.SequenceActions;
41
42/**
43 * An implementation of the Application class that automatically provides
44 * an instance of a REST server. This application class is intended to be
45 * a single-server implementation. Any attempt to bind additional servers
46 * will throw an error.
47 *
48 */
49export class RestApplication extends Application implements HttpServerLike {
50 /**
51 * The main REST server instance providing REST API for this application.
52 */
53 get restServer(): RestServer {
54 // FIXME(kjdelisle): I attempted to mimic the pattern found in RestServer
55 // with no success, so until I've got a better way, this is functional.
56 return this.getSync<RestServer>('servers.RestServer');
57 }
58
59 /**
60 * Handle incoming HTTP(S) request by invoking the corresponding
61 * Controller method via the configured Sequence.
62 *
63 * @example
64 *
65 * ```ts
66 * const app = new RestApplication();
67 * // setup controllers, etc.
68 *
69 * const server = http.createServer(app.requestHandler);
70 * server.listen(3000);
71 * ```
72 *
73 * @param req - The request.
74 * @param res - The response.
75 */
76 get requestHandler(): HttpRequestListener {
77 return this.restServer.requestHandler;
78 }
79
80 /**
81 * Create a REST application with the given parent context
82 * @param parent - Parent context
83 */
84 constructor(parent: Context);
85 /**
86 * Create a REST application with the given configuration and parent context
87 * @param config - Application configuration
88 * @param parent - Parent context
89 */
90 constructor(config?: ApplicationConfig, parent?: Context);
91
92 constructor(configOrParent?: ApplicationConfig | Context, parent?: Context) {
93 super(configOrParent, parent);
94 this.component(RestComponent);
95 }
96
97 server(server: Constructor<Server>, name?: string): Binding {
98 if (this.findByTag('server').length > 0) {
99 throw new Error(ERR_NO_MULTI_SERVER);
100 }
101 return super.server(server, name);
102 }
103
104 sequence(sequence: Constructor<SequenceHandler>): Binding {
105 return this.restServer.sequence(sequence);
106 }
107
108 handler(handlerFn: SequenceFunction) {
109 this.restServer.handler(handlerFn);
110 }
111
112 /**
113 * Mount static assets to the REST server.
114 * See https://expressjs.com/en/4x/api.html#express.static
115 * @param path - The path(s) to serve the asset.
116 * See examples at https://expressjs.com/en/4x/api.html#path-examples
117 * To avoid performance penalty, `/` is not allowed for now.
118 * @param rootDir - The root directory from which to serve static assets
119 * @param options - Options for serve-static
120 */
121 static(path: PathParams, rootDir: string, options?: ServeStaticOptions) {
122 this.restServer.static(path, rootDir, options);
123 }
124
125 /**
126 * Bind a body parser to the server context
127 * @param parserClass - Body parser class
128 * @param address - Optional binding address
129 */
130 bodyParser(
131 bodyParserClass: Constructor<BodyParser>,
132 address?: BindingAddress<BodyParser>,
133 ): Binding<BodyParser> {
134 return this.restServer.bodyParser(bodyParserClass, address);
135 }
136
137 /**
138 * Configure the `basePath` for the rest server
139 * @param path - Base path
140 */
141 basePath(path = '') {
142 this.restServer.basePath(path);
143 }
144
145 /**
146 * Bind an Express middleware to this server context
147 *
148 * @example
149 * ```ts
150 * import myExpressMiddlewareFactory from 'my-express-middleware';
151 * const myExpressMiddlewareConfig= {};
152 * const myExpressMiddleware = myExpressMiddlewareFactory(myExpressMiddlewareConfig);
153 * server.expressMiddleware('middleware.express.my', myExpressMiddleware);
154 * ```
155 * @param key - Middleware binding key
156 * @param middleware - Express middleware handler function(s)
157 *
158 */
159 expressMiddleware(
160 key: BindingAddress,
161 middleware: ExpressRequestHandler | ExpressRequestHandler[],
162 options?: MiddlewareBindingOptions,
163 ): Binding<Middleware>;
164
165 /**
166 * Bind an Express middleware to this server context
167 *
168 * @example
169 * ```ts
170 * import myExpressMiddlewareFactory from 'my-express-middleware';
171 * const myExpressMiddlewareConfig= {};
172 * server.expressMiddleware(myExpressMiddlewareFactory, myExpressMiddlewareConfig);
173 * ```
174 * @param middlewareFactory - Middleware module name or factory function
175 * @param middlewareConfig - Middleware config
176 * @param options - Options for registration
177 *
178 * @typeParam CFG - Configuration type
179 */
180 expressMiddleware<CFG>(
181 middlewareFactory: ExpressMiddlewareFactory<CFG>,
182 middlewareConfig?: CFG,
183 options?: MiddlewareBindingOptions,
184 ): Binding<Middleware>;
185
186 expressMiddleware<CFG>(
187 factoryOrKey: ExpressMiddlewareFactory<CFG> | BindingAddress<Middleware>,
188 configOrHandlers: CFG | ExpressRequestHandler | ExpressRequestHandler[],
189 options: MiddlewareBindingOptions = {},
190 ): Binding<Middleware> {
191 return this.restServer.expressMiddleware(
192 factoryOrKey,
193 configOrHandlers,
194 options,
195 );
196 }
197
198 /**
199 * Register a middleware function or provider class
200 *
201 * @example
202 * ```ts
203 * const log: Middleware = async (requestCtx, next) {
204 * // ...
205 * }
206 * server.middleware(log);
207 * ```
208 *
209 * @param middleware - Middleware function or provider class
210 * @param options - Middleware binding options
211 */
212 middleware(
213 middleware: Middleware | Constructor<Provider<Middleware>>,
214 options: MiddlewareBindingOptions = {},
215 ): Binding<Middleware> {
216 return this.restServer.middleware(middleware, options);
217 }
218
219 /**
220 * Register a new Controller-based route.
221 *
222 * @example
223 * ```ts
224 * class MyController {
225 * greet(name: string) {
226 * return `hello ${name}`;
227 * }
228 * }
229 * app.route('get', '/greet', operationSpec, MyController, 'greet');
230 * ```
231 *
232 * @param verb - HTTP verb of the endpoint
233 * @param path - URL path of the endpoint
234 * @param spec - The OpenAPI spec describing the endpoint (operation)
235 * @param controllerCtor - Controller constructor
236 * @param controllerFactory - A factory function to create controller instance
237 * @param methodName - The name of the controller method
238 */
239 route<T extends object>(
240 verb: string,
241 path: string,
242 spec: OperationObject,
243 controllerCtor: ControllerClass<T>,
244 controllerFactory: ControllerFactory<T>,
245 methodName: string,
246 ): Binding;
247
248 /**
249 * Register a new route invoking a handler function.
250 *
251 * @example
252 * ```ts
253 * function greet(name: string) {
254 * return `hello ${name}`;
255 * }
256 * app.route('get', '/', operationSpec, greet);
257 * ```
258 *
259 * @param verb - HTTP verb of the endpoint
260 * @param path - URL path of the endpoint
261 * @param spec - The OpenAPI spec describing the endpoint (operation)
262 * @param handler - The function to invoke with the request parameters
263 * described in the spec.
264 */
265 route(
266 verb: string,
267 path: string,
268 spec: OperationObject,
269 handler: Function,
270 ): Binding;
271
272 /**
273 * Register a new route.
274 *
275 * @example
276 * ```ts
277 * function greet(name: string) {
278 * return `hello ${name}`;
279 * }
280 * const route = new Route('get', '/', operationSpec, greet);
281 * app.route(route);
282 * ```
283 *
284 * @param route - The route to add.
285 */
286 route(route: RouteEntry): Binding;
287
288 /**
289 * Register a new route.
290 *
291 * @example
292 * ```ts
293 * function greet(name: string) {
294 * return `hello ${name}`;
295 * }
296 * app.route('get', '/', operationSpec, greet);
297 * ```
298 */
299 route(
300 verb: string,
301 path: string,
302 spec: OperationObject,
303 handler: Function,
304 ): Binding;
305
306 route<T extends object>(
307 routeOrVerb: RouteEntry | string,
308 path?: string,
309 spec?: OperationObject,
310 controllerCtorOrHandler?: ControllerClass<T> | Function,
311 controllerFactory?: ControllerFactory<T>,
312 methodName?: string,
313 ): Binding {
314 const server = this.restServer;
315 if (typeof routeOrVerb === 'object') {
316 return server.route(routeOrVerb);
317 } else if (arguments.length === 4) {
318 return server.route(
319 routeOrVerb,
320 path!,
321 spec!,
322 controllerCtorOrHandler as Function,
323 );
324 } else {
325 return server.route(
326 routeOrVerb,
327 path!,
328 spec!,
329 controllerCtorOrHandler as ControllerClass<T>,
330 controllerFactory!,
331 methodName!,
332 );
333 }
334 }
335
336 /**
337 * Register a route redirecting callers to a different URL.
338 *
339 * @example
340 * ```ts
341 * app.redirect('/explorer', '/explorer/');
342 * ```
343 *
344 * @param fromPath - URL path of the redirect endpoint
345 * @param toPathOrUrl - Location (URL path or full URL) where to redirect to.
346 * If your server is configured with a custom `basePath`, then the base path
347 * is prepended to the target location.
348 * @param statusCode - HTTP status code to respond with,
349 * defaults to 303 (See Other).
350 */
351 redirect(
352 fromPath: string,
353 toPathOrUrl: string,
354 statusCode?: number,
355 ): Binding {
356 return this.restServer.redirect(fromPath, toPathOrUrl, statusCode);
357 }
358
359 /**
360 * Set the OpenAPI specification that defines the REST API schema for this
361 * application. All routes, parameter definitions and return types will be
362 * defined in this way.
363 *
364 * Note that this will override any routes defined via decorators at the
365 * controller level (this function takes precedent).
366 *
367 * @param spec - The OpenAPI specification, as an object.
368 * @returns Binding for the api spec
369 */
370 api(spec: OpenApiSpec): Binding {
371 return this.restServer.bind(RestBindings.API_SPEC).to(spec);
372 }
373
374 /**
375 * Mount an Express router to expose additional REST endpoints handled
376 * via legacy Express-based stack.
377 *
378 * @param basePath - Path where to mount the router at, e.g. `/` or `/api`.
379 * @param router - The Express router to handle the requests.
380 * @param spec - A partial OpenAPI spec describing endpoints provided by the
381 * router. LoopBack will prepend `basePath` to all endpoints automatically.
382 * This argument is optional. You can leave it out if you don't want to
383 * document the routes.
384 */
385 mountExpressRouter(
386 basePath: string,
387 router: ExpressRequestHandler,
388 spec?: RouterSpec,
389 ): void {
390 this.restServer.mountExpressRouter(basePath, router, spec);
391 }
392
393 /**
394 * Export the OpenAPI spec to the given json or yaml file
395 * @param outFile - File name for the spec. The extension of the file
396 * determines the format of the file.
397 * - `yaml` or `yml`: YAML
398 * - `json` or other: JSON
399 * If the outFile is not provided or its value is `''` or `'-'`, the spec is
400 * written to the console using the `log` function.
401 * @param log - Log function, default to `console.log`
402 */
403 async exportOpenApiSpec(outFile = '', log = console.log): Promise<void> {
404 return this.restServer.exportOpenApiSpec(outFile, log);
405 }
406}