'Serverless database setup with dependecy injection

So, I'm working on a serverless lambda with express and TypeORM

I'm having issues to instance the database and then use it in the repository class to retreive data.

// handler.ts
require('source-map-support/register');
import App from './features/app';
import LeadsController from './features/leads/leads.controller';
import { Container } from 'typedi';

import 'source-map-support/register';
import serverlessExpress from '@vendia/serverless-express';
import { Context } from 'aws-lambda';

const setup = async (event: any, context: Context) => {
  const app = new App([Container.get(LeadsController)], 3000);
  await app.init();
  return serverlessExpress({ app: app.app })(event, context, () => {
    console.log('callback');
  });
};

const handler = (event: any, context: Context) => {
  return setup(event, context);
};

exports.handler = handler;
// app.ts
import express, { Router } from 'express';
import bodyParser from 'body-parser';
import 'reflect-metadata';
import { Database } from '../common/database/db';

class App {
  public app: express.Application;
  public port: number;

  constructor(controllers: any[], port: number) {
    this.app = express();
    this.port = port;
    this.initializeMiddlewares();
    this.initializeControllers(controllers);
    this.endMiddlewares();
  }

  async init() {
    const database = new Database(); // HERE IS WHERE MY PROBLEM IS, I CAN'T FIND ANOTHER WAY TO INIT THE DATABASE
    await database.connect();
  }
  private initializeMiddlewares() {
    this.app.use(bodyParser.json());
  }

  private initializeControllers(controllers: any[]) {
    const router: Router[] = [];
    controllers.forEach((controller) => {
      router.push(controller.router);
    });
    this.app.use('/api', router);
  }

  private endMiddlewares() {
    // @todo: set up error handler;
  }
}

export default App;
// repository.ts
import { Service } from 'typedi';
import { Database } from '../../common/database/db';
import { Lead } from './leads.entity';

@Service()
export class LeadsRepository {
  private repository;
  constructor(private readonly db: Database) {
    console.log(db); // AND HERE IS MY OTHER PROBLEM
    this.repository = this.db.dataSource.getRepository(Lead);
  }

  async findAll() {
    return await this.repository.find();
  }
}

// db.ts
import { DataSource } from 'typeorm';
import { Lead } from '../../features/leads/leads.entity';
import { Interest } from '../../features/interests/interests.entity';
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions';
import { Service } from 'typedi';

/**
 * Database manager class
 */
@Service()
export class Database {
  dataSource: DataSource;

  constructor() {}

  async connect() {
    const connectionOptions: MysqlConnectionOptions = {
      type: 'mysql',
      host: 'localhost',
      port: 33061,
      username: 'root',
      password: 'root',
      database: 'leads-interests',
      synchronize: true,
      dropSchema: true,
      logging: 'all',
      entities: [Lead, Interest],
      subscribers: [],
      migrations: [__dirname + '/migration.{ts,js}'],
      migrationsTableName: '_migration'
    };
    this.dataSource = new DataSource(connectionOptions);
    await this.dataSource.initialize();
    console.log('Connected');
  }
}

After a lot of thinking and learning about dependency injection I reached the conclusion that my problem is where I instance the database in the first place, but tbh I don't know where else should I do it since it's an async call and I don't wanna be creating new instance everytime I have to retreive or insert data.

Does anyone have another workaround or a better code architecture that I can use?

In the end, what I need is to always use the same database instance



Solution 1:[1]

I see that the app constructor has controllers(which in turn depends on the repository and then database?) in its dependencies and before the database connection function can complete its task the controller has already been resolved from the container.

To resolve this, try instantiating the database first in the setup function and explicitly set the database in the container like so:

const setup = async (event: any, context: Context) => {
  const database = new Database();
  await database.connect();
  Container.set(Database,database); //import Container from 'typedi'
  const app = new App([Container.get(LeadsController)], 3000);
  await app.init();
  return serverlessExpress({ app: app.app })(event, context, () => {
    console.log('callback');
  });
};

Or even better, initialize the datasource on setup function and create a custom identifier for it and inject datasource using the injection token in your repository.

In general though, I would not recommend performing Dependency Injection on serverless functions because it will increase the cold start times and you should try to optimize your application startup time in a serverless environment.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shahriar Shojib