Skip to content

Instantly share code, notes, and snippets.

@timwhit
Last active July 31, 2022 14:42
Show Gist options
  • Save timwhit/3d44de267dd727b72780cbb348b315c3 to your computer and use it in GitHub Desktop.
Save timwhit/3d44de267dd727b72780cbb348b315c3 to your computer and use it in GitHub Desktop.
TypeScript + Node.js Enterprise Patterns
import * as express from 'express';
import {injectable, inject} from 'inversify';
import TYPES from '../types';
import {AddressService} from '../service/AddressService';
import {Address} from '../model/Address';
import {RegistrableController} from './RegisterableController';
@injectable()
export class AddressController implements RegistrableController {
private addressService: AddressService;
constructor(@inject(TYPES.AddressService) addressService: AddressService) {
this.addressService = addressService;
}
public register(app: express.Application): void {
app.route('/')
.get(async(req: express.Request, res: express.Response, next: express.NextFunction) => {
const addresses = await this.addressService.getAddresses().catch(err => next(err));
res.json(addresses);
})
.post(async(req: express.Request, res: express.Response, next: express.NextFunction) => {
const address = new Address(
req.body.address1,
req.body.address2,
req.body.city,
req.body.state,
req.body.zip,
req.body.country
);
const createdAddress = await this.addressService.createAddress(address).catch(err => next(err));
res.json(createdAddress);
});
app.route('/:id')
.get(async(req: express.Request, res: express.Response, next: express.NextFunction) => {
const addresses = await this.addressService.getAddress(<string> req.params.id).catch(err => next(err));
res.json(addresses);
})
.put(async(req: express.Request, res: express.Response, next: express.NextFunction) => {
const address = new Address(
req.body.address1,
req.body.address2,
req.body.city,
req.body.state,
req.body.zip,
req.body.country,
req.body.id
);
const updatedAddress = await this.addressService.updateAddress(address).catch(err => next(err));
res.json(updatedAddress);
});
}
}
import {injectable} from 'inversify';
import {mongoDatabase, AddressDTO, AddressMongoSchema, AddressDbSchema} from '../model/AddressSchema';
import {logger} from '../util/Logger';
import {createConnection, Connection, Repository, ConnectionOptions} from 'typeorm';
export interface AddressRepository {
findAll(): Promise<Array<AddressDTO>>;
create(addressDTO: AddressDTO): Promise<AddressDTO>;
update(addressDTO: AddressDTO): Promise<AddressDTO>;
find(id: string): Promise<AddressDTO>;
}
@injectable()
export class AddressRepositoryImplMongo implements AddressRepository {
public async findAll(): Promise<Array<AddressDTO>> {
const addressDTOs = await mongoDatabase.connect().then(() => mongoDatabase.Addresses.find());
return addressDTOs.toArray();
}
public async create(addressDTO: AddressDTO): Promise<AddressDTO> {
return await mongoDatabase.connect().then(() => mongoDatabase.Addresses.create(addressDTO));
}
public async update(addressDTO: AddressDTO): Promise<AddressDTO> {
const dto: AddressMongoSchema = await mongoDatabase.connect().then(() => mongoDatabase.Addresses.findOne(addressDTO._id));
dto.address1 = addressDTO.address1;
if (addressDTO.address2) {
dto.address2 = addressDTO.address2;
} else {
// undefined isn't handled by mongo, so set to null
dto.address2 = null;
}
dto.city = addressDTO.city;
dto.city = addressDTO.city;
dto.zip = addressDTO.zip;
dto.country = addressDTO.country;
const saved = await dto.save((err: Error, a: AddressDTO) => {
if (err) {
logger.error('Error updating address: ' + err);
throw err;
}
return a;
});
return saved;
}
public async find(id: string): Promise<AddressDTO> {
return await mongoDatabase.connect().then(() => mongoDatabase.Addresses.findOne(id));
}
}
@injectable()
export class AddressRepositoryImplDb implements AddressRepository {
private addressRepository: Repository<AddressDbSchema>;
constructor() {
this.connect().then(async connection => {
this.addressRepository = connection.getRepository(AddressDbSchema);
}).catch(err => logger.error('Cannot connect to database', err));
}
public async findAll(): Promise<Array<AddressDTO>> {
return await this.addressRepository.find();
}
public async create(addressDTO: AddressDTO): Promise<AddressDTO> {
return await this.addressRepository.persist(addressDTO);
}
public async update(addressDTO: AddressDTO): Promise<AddressDTO> {
return await this.addressRepository.persist(addressDTO);
}
public async find(id: string): Promise<AddressDTO> {
return await this.addressRepository.findOneById(id);
}
private connect(): Promise<Connection> {
return createConnection(<ConnectionOptions> {
driver: {
type: 'sqlite',
storage: 'tmp/sqlitedb.db'
},
logging: {
logQueries: true,
logSchemaCreation: true
},
autoSchemaSync: true,
entities: [
AddressDbSchema
]
});
}
}
import {Core, Model, Instance, Collection, Index, Property, ObjectID} from 'iridium';
import {Table, Column, PrimaryColumn} from 'typeorm';
export interface AddressDTO {
_id?: string;
address1: string;
address2?: string;
city: string;
state: string;
zip: string;
country: string;
}
/**
* Iridium config
*/
@Index({name: 1})
@Collection('addresses')
export class AddressMongoSchema extends Instance<AddressDTO, AddressMongoSchema> implements AddressDTO {
@ObjectID
// tslint:disable-next-line:variable-name
public _id: string;
@Property(String, true)
public address1: string;
@Property(String, false)
public address2: string;
@Property(String, true)
public city: string;
@Property(String, true)
public state: string;
@Property(String, true)
public zip: string;
@Property(String, true)
public country: string;
}
class AddressDatabase extends Core {
public Addresses = new Model<AddressDTO, AddressMongoSchema>(this, AddressMongoSchema);
}
export const mongoDatabase = new AddressDatabase({database: 'test_db'});
/**
* TypeORM Schema Config
*/
@Table('address')
export class AddressDbSchema implements AddressDTO {
@PrimaryColumn()
// tslint:disable-next-line:variable-name
public _id?: string;
@Column()
public address1: string;
@Column()
public address2?: string;
@Column()
public city: string;
@Column()
public state: string;
@Column()
public zip: string;
@Column()
public country: string;
}
import {injectable, inject} from 'inversify';
import {Address} from '../model/Address';
import {AddressRepository} from '../repository/AddressRepository';
import TYPES from '../types';
import 'reflect-metadata';
import {AddressDTO} from '../model/AddressSchema';
import * as _ from 'lodash';
export interface AddressService {
getAddresses(): Promise<Array<Address>>;
createAddress(address: Address): Promise<Address>;
updateAddress(address: Address): Promise<Address>;
getAddress(id: string): Promise<Address>;
}
@injectable()
export class AddressServiceImpl implements AddressService {
@inject(TYPES.AddressRepository)
private addressRepositoryMongo: AddressRepository;
@inject(TYPES.AddressRepository2)
private addressRepositoryDb: AddressRepository;
public async getAddresses(): Promise<Array<Address>> {
// grab addresses from mongo
const addressesMongo: Array<Address> = await this.addressRepositoryMongo.findAll().then((a) => a.map((dto: AddressDTO) => {
return this.toAddressDTO(dto);
}));
// grab addresses from db
const addressesDb: Array<Address> = await this.addressRepositoryDb.findAll().then((a2) => a2.map((dto: AddressDTO) => {
return this.toAddressDTO(dto);
}));
return _.uniqBy(addressesMongo.concat(addressesDb), 'id');
}
public async createAddress(address: Address): Promise<Address> {
const addressDTO: AddressDTO = this.toAddress(address);
const createdDTO: AddressDTO = await this.addressRepositoryMongo.create(addressDTO);
// duplicates the address in the DB
await this.addressRepositoryDb.create(await createdDTO);
return await this.toAddressDTO(createdDTO);
}
public async updateAddress(address: Address): Promise<Address> {
const addressDTO: AddressDTO = this.toAddress(address);
const updated: AddressDTO = await this.addressRepositoryMongo.update(addressDTO);
// update db address
await this.addressRepositoryDb.update(updated);
return await this.toAddressDTO(updated);
}
public async getAddress(id: string): Promise<Address> {
let address = await this.addressRepositoryMongo.find(id).then((a) => {
return this.toAddressDTO(a);
});
if (!address) {
address = await this.addressRepositoryDb.find(id).then((a) => {
return this.toAddressDTO(a);
});
}
return address;
}
private toAddress(address: Address): AddressDTO {
return {
address1: address.getAddress1,
address2: address.getAddress2,
city: address.getCity,
state: address.getState,
zip: address.getZip,
country: address.getCountry,
_id: address.getId
};
}
private toAddressDTO(addressDTO: AddressDTO): Address {
return new Address(
addressDTO.address1,
addressDTO.address2,
addressDTO.city,
addressDTO.state,
addressDTO.zip,
addressDTO.country,
addressDTO._id.toString());
}
}
import * as express from 'express';
import * as bodyParser from 'body-parser';
import TYPES from './types';
import container from './inversify.config';
import {logger} from './util/Logger';
import {RegistrableController} from './controller/RegisterableController';
// create express application
const app: express.Application = express();
// let express support JSON bodies
app.use(bodyParser.json());
// grabs the Controller from IoC container and registers all the endpoints
const controllers: RegistrableController[] = container.getAll<RegistrableController>(TYPES.Controller);
controllers.forEach(controller => controller.register(app));
// setup express middleware logging and error handling
app.use(function (err: Error, req: express.Request, res: express.Response, next: express.NextFunction) {
logger.error(err.stack);
next(err);
});
app.use(function (err: Error, req: express.Request, res: express.Response, next: express.NextFunction) {
res.status(500).send('Internal Server Error');
});
app.listen(3000, function () {
logger.info('Example app listening on port 3000!');
});
import {Container} from 'inversify';
import TYPES from './types';
import {AddressService, AddressServiceImpl} from './service/AddressService';
import {AddressRepository, AddressRepositoryImplMongo, AddressRepositoryImplDb} from './repository/AddressRepository';
import {AddressController} from './controller/AddressController';
import {RegistrableController} from './controller/RegisterableController';
const container = new Container();
container.bind<RegistrableController>(TYPES.Controller).to(AddressController);
container.bind<AddressService>(TYPES.AddressService).to(AddressServiceImpl);
container.bind<AddressRepository>(TYPES.AddressRepository).to(AddressRepositoryImplMongo);
container.bind<AddressRepository>(TYPES.AddressRepository2).to(AddressRepositoryImplDb);
export default container;
import * as express from 'express';
export interface RegistrableController {
register(app: express.Application): void;
}
const TYPES = {
AddressRepository: Symbol('AddressRepository'),
AddressRepository2: Symbol('AddressRepository2'),
AddressService: Symbol('AddressService'),
Controller: Symbol('Controller')
};
export default TYPES;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment