import express, { Express } from 'express' import { Application as FeathersApplication, defaultServiceMethods } from '@feathersjs/feathers' import { routing } from '@feathersjs/transport-commons' import { createDebug } from '@feathersjs/commons' import cors from 'cors' import compression from 'compression' import { rest, RestOptions, formatter } from './rest' import { errorHandler, notFound, ErrorHandlerOptions } from './handlers' import { Application, ExpressOverrides } from './declarations' import { AuthenticationSettings, authenticate, parseAuthentication } from './authentication' import { default as original, static as serveStatic, json, raw, text, urlencoded, query, Router } from 'express' export { original, serveStatic, serveStatic as static, json, raw, text, urlencoded, query, rest, Router, RestOptions, formatter, errorHandler, notFound, Application, ErrorHandlerOptions, ExpressOverrides, AuthenticationSettings, parseAuthentication, authenticate, cors, compression } const debug = createDebug('@feathersjs/express') export default function feathersExpress( feathersApp?: FeathersApplication, expressApp: Express = express() ): Application { if (!feathersApp) { return expressApp as any } if (typeof feathersApp.setup !== 'function') { throw new Error('@feathersjs/express requires a valid Feathers application instance') } const app = expressApp as any as Application const { use: expressUse, listen: expressListen } = expressApp as any const { use: feathersUse, teardown: feathersTeardown } = feathersApp Object.assign(app, { use(location: string & keyof S, ...rest: any[]) { let service: any let options = {} const middleware = rest.reduce( function (middleware, arg) { if (typeof arg === 'function' || Array.isArray(arg)) { middleware[service ? 'after' : 'before'].push(arg) } else if (!service) { service = arg } else if (arg.methods || arg.events || arg.express || arg.koa) { options = arg } else { throw new Error('Invalid options passed to app.use') } return middleware }, { before: [], after: [] } ) const hasMethod = (methods: string[]) => methods.some((name) => service && typeof service[name] === 'function') // Check for service (any object with at least one service method) if (hasMethod(['handle', 'set']) || !hasMethod(defaultServiceMethods)) { debug('Passing app.use call to Express app') return expressUse.call(this, location, ...rest) } debug('Registering service with middleware', middleware) // Since this is a service, call Feathers `.use` feathersUse.call(this, location, service, { express: middleware, ...options }) return this }, async listen(...args: any[]) { const server = expressListen.call(this, ...args) this.server = server await this.setup(server) debug('Feathers application listening') return server } } as Application) const appDescriptors = { ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(app)), ...Object.getOwnPropertyDescriptors(app) } const newDescriptors = { ...Object.getOwnPropertyDescriptors(Object.getPrototypeOf(feathersApp)), ...Object.getOwnPropertyDescriptors(feathersApp) } // Copy all non-existing properties (including non-enumerables) // that don't already exist on the Express app Object.keys(newDescriptors).forEach((prop) => { const appProp = appDescriptors[prop] const newProp = newDescriptors[prop] if (appProp === undefined && newProp !== undefined) { Object.defineProperty(expressApp, prop, newProp) } }) // Assign teardown and setup which will also make sure that hooks are initialized app.setup = feathersApp.setup as any app.teardown = async function teardown(server?: any) { return feathersTeardown.call(this, server).then( () => new Promise((resolve, reject) => { if (this.server) { this.server.close((e) => (e ? reject(e) : resolve(this))) } else { resolve(this) } }) ) } app.configure(routing() as any) app.use((req, _res, next) => { req.feathers = { ...req.feathers, provider: 'rest' } return next() }) return app } if (typeof module !== 'undefined') { module.exports = Object.assign(feathersExpress, module.exports) }