TypeScript+Express+Tsyringe构建API遇router未定义错误求助
TypeError: Cannot read properties of undefined (reading 'router') 问题解决
我正在使用TypeScript和ExpressJS,采用整洁架构构建类式RESTful API,同时练习Typegoose和Tsyringe以学习该语言与框架下的依赖注入实现。但访问路径时触发TypeError: Cannot read properties of undefined (reading 'router')错误。
相关代码
Product 模型
import { getModelForClass, prop } from '@typegoose/typegoose'; export class ProductModel { @prop() private timestamp: number = Date.now(); @prop({ required: true }) private title!: string; @prop({ required: true }) private description!: string; @prop({ required: true }) private code!: number; @prop({ required: true }) private picture!: string; @prop({ required: true }) private price!: number; @prop({ required: true }) private stock!: number; } export const Product = getModelForClass(ProductModel);
Database Repository 接口
import { Id, Query } from '../commons/commons'; export interface DatabaseRepository<T> { getAll(query?: Query): Promise<T[]>; }
MongoDB 适配器
import { Product, ProductModel } from '../../../../domain/models/product/Product'; import { DatabaseRepository } from '../../../../application/repository/database.repository'; import { Id, Query } from '../../../../application/commons/commons'; import { injectable } from 'tsyringe'; import { Logger } from '../../../../application/config/logger/logger'; @injectable() export class ProductMongoDBAdapter implements DatabaseRepository<ProductModel> { async getAll(query?: Query): Promise<ProductModel[]> { try { return Product.find({}); } catch (error) { Logger.error(error); throw (error); } } }
UseCase
import { autoInjectable, inject } from 'tsyringe'; import { DatabaseRepository } from '../../../application/repository/database.repository'; import { ProductModel } from '../../models/product/Product'; @autoInjectable() export class GetAllProductsUseCase { constructor(@inject('DatabaseRepository') private databaseRepository: DatabaseRepository<ProductModel>) { } public async getAllProducts(): Promise<ProductModel[]> { return this.databaseRepository.getAll(); } }
Controller
import { autoInjectable } from 'tsyringe'; import * as USE_CASE from '../../../../domain/usecases'; import { Logger } from '../../../../application/config/logger/logger'; import { Request, Response } from 'express'; @autoInjectable() export class ProductHandler { constructor(private readonly getAllProductsUseCase: USE_CASE.GetAllProductsUseCase) { } public async getAllProducts(req: Request, res: Response): Promise<Response> { try { const products = await this.getAllProductsUseCase.getAllProducts(); return res.status(200).json(products); } catch (error) { Logger.error(error); throw(error); } } }
Product Router
import { Router } from 'express'; import * as PRODUCT_HANDLER from '../../rest-controller/'; import { autoInjectable } from 'tsyringe'; @autoInjectable() export class ProductRouter { private readonly router: Router = Router(); constructor(private readonly productHandler: PRODUCT_HANDLER.ProductHandler) { } public routes() { this.router .route('/api/products') .get(this.productHandler.getAllProducts); } }
主路由配置
import { Router } from 'express'; import { autoInjectable } from 'tsyringe'; import { ProductRouter } from './product/product.routes'; @autoInjectable() export default class Routes { private router: Router = Router(); constructor(private readonly productRouter: ProductRouter) { } public routes() { return this.router.use('/', this.productRouter.routes); } }
应用启动类
import 'reflect-metadata'; import express, { Application, json } from 'express'; import morgan from 'morgan'; import cors from 'cors'; import { Server } from 'http'; import { Gen } from './commons/commons'; import { Logger } from './config/logger/logger'; import DbConnection from './config/database/DbConnection'; import Config from './config/Config'; import Routes from '../infrastructure/entry-points/routes/routes'; import { autoInjectable, container } from 'tsyringe'; import { ProductMongoDBAdapter } from '../infrastructure/driven-adapters/mongo-adapter/product/product.mongodb.adapter'; @autoInjectable() export default class ServerApplication { private app: Application = express(); private routes: Routes; constructor() { container.registerInstance('DatabaseRepository', new ProductMongoDBAdapter()); this.routes = container.resolve(Routes); this.middlewares(); } public init(PORT: Gen): Server { return this.app.listen(PORT, () => { Logger.info(`Open server on http://localhost:${PORT}`); DbConnection.connect(`${Config.DB_CONNECTION}`) .then(() => Logger.info('Database connection established successfully')) .catch(err => Logger.error(err)); }); } private middlewares() { this.app.use(cors()); this.app.use(json()); this.app.use(morgan('dev')); this.app.use('/', this.routes.routes); } }
服务器启动文件
import 'reflect-metadata'; import ServerApplication from './application/ServerApplication'; import Config from './application/config/Config'; import { Gen } from './application/commons/commons'; import { Logger } from './application/config/logger/logger'; export class Server { private static PORT: Gen = Config.PORT; private static serverApplication: ServerApplication = new ServerApplication(); public static start(): void { try { this.serverApplication.init(`${Server.PORT}`); } catch (error) { Logger.error(error); } } } Server.start();
错误信息
TypeError: Cannot read properties of undefined (reading 'router') at routes (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\src\infrastructure\entry-points\routes\routes.ts:13:17) at Layer.handle [as handle_request] (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\layer.js:95:5) at trim_prefix (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:317:13) at C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:284:7 at Function.process_params (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:335:12) at next (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:275:10) at logger (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\morgan@1.9.1\node_modules\morgan\index.js:144:5) at Layer.handle [as handle_request] (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\layer.js:95:5) at trim_prefix (C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:317:13) at C:\Users\juan.cano\Desktop\Study\C-1\BACK-END\ProyectoFinal\EntregaFinal\node_modules\.pnpm\express@4.16.1\node_modules\express\lib\router\index.js:284:7
问题修复方案
1. 修改ProductRouter,返回Router实例并修复this指向
ProductRouter的routes方法需要返回配置好的Router对象,同时要确保getAllProducts方法执行时this指向正确:
import { Router } from 'express'; import * as PRODUCT_HANDLER from '../../rest-controller/'; import { autoInjectable } from 'tsyringe'; @autoInjectable() export class ProductRouter { private readonly router: Router = Router(); constructor(private readonly productHandler: PRODUCT_HANDLER.ProductHandler) {} public routes(): Router { // 用箭头函数绑定this,避免productHandler的this丢失 this.router .route('/api/products') .get((req, res) => this.productHandler.getAllProducts(req, res)); return this.router; } }
2. 修改主Routes,正确调用ProductRouter的routes方法
原代码中直接传递方法引用,需要改为调用方法获取Router实例:
import { Router } from 'express'; import { autoInjectable } from 'tsyringe'; import { ProductRouter } from './product/product.routes'; @autoInjectable() export default class Routes { private router: Router = Router(); constructor(private readonly productRouter: ProductRouter) {} public routes(): Router { return this.router.use('/', this.productRouter.routes()); } }
3. 修改ServerApplication,正确调用Routes的routes方法
同样需要调用方法获取Router实例,而不是传递方法引用:
private middlewares() { this.app.use(cors()); this.app.use(json()); this.app.use(morgan('dev')); this.app.use('/', this.routes.routes()); }
原因解析
ProductRouter的routes方法未返回Router实例,导致主Routes调用后得到undefined,进而Express在尝试访问router属性时报错。- 直接传递方法引用会导致执行时
this指向丢失,无法正确访问类内部的属性和方法。 - 所有路由配置方法都需要返回有效的Router对象,才能被Express的
use方法正确处理。
内容的提问来源于stack exchange,提问作者JpCano
相关产品推荐
相关产品推荐

