How can I reduce amount of boilerplate code while working with TypeScript+mongoose 🙈
Tags: requestforpost, typescript, mongoose
Published: 2019-02-28 11:07:03

---
title: How can I reduce amount of boilerplate code while working with TypeScript+mongoose 🙈
published: true
description: 
tags: #requestforpost, #typescript, #mongoose
---


Let's say I have two entities: *Vehicle* and *Tariff*. They are related as 1-to-many (vehicle can has many tariffs). 

This is my basic types definition of objects, I want to work with:

```typescript
interface Tariff {
    id: string;			// <-- ID of entity
    createdAt: Date; 	// <-- some meta data fields for sorting and etc.
    
	days: number;  		// <-- userful info
	price: number;		// <-
}

interface Vehicle {
    id: string;			 // <-- ID of entity
    createdAt: Date; 	 // <-- some meta data fields for sorting and etc.
    
    model: string;		 // <-- userful info
    year?: string;       // <-
    seatsCount?: number; // <-
    tariffs: Tariff[];   // <-- array of tariffs
}
```



Looks great. No we need to create database model. First let's define mongoose schema:

```typescript
import mongoose from "mongoose";

const vehicleSchema = new mongoose.Schema({
	createdAt: {type: Date, required: true, default: () => new Date()},

    model: {type: String, required: true},
    year: String,
    seatsCount: Number,
});
```

Awesome. I decided no to put tariffs inside vehicle documents and they will be stored separately. 

Next we need to create mongoose model. But we need to provide generic type extending `mongoose.Document` to `mongoose.model`. And this is where code duplication begins:

```typescript
// First we need to define TypeScript schema equalent of mongoose schema:
interface VehicleSchema {
    createdAt: Date;

    model: string;
    year?: string;
    seatsCount?: number;
}

// Then we need to defined a new type of out vehicle models:
type VehicleModel = VehicleSchema && mongoose.Document;

// And finaly we can create model:
const VehicleModel = mongoose.model<VehicleModel>("Car", carSchema);

// Look, here is a type named "VehicleModel" and a constant named "VehicleModel"


```



Now we can write functions for CRUD operations:

```typescript
namespace vehicle {
    export const create = async (form: VehicleCreateForm): Promise<Vehicle> => {
		const fields = toDb(form);
        
        // assume form is prevalidated
        let dbVehicle = new VehicleModel(form);
        dbVehicle = await model.save();
        
      	// tariff - is the separate module, like this one,  
        // createAllFor methods retuns Tariff[]
        const tariffs = await tariff.createAllFor(dbVehicle.id, form.tariffs);
        
        return fromDb(dbVehicle, tariffs);
    }
    
    //...
    
    // export const update = ...
    // export const list = ...
	// export const get = ...    
    // etc..
}
```

Here we intoduce one extra type: `VehicleCreateForm`, it describes all fields needed to create vehicle:

```typescript
interface VehicleCreateForm {
    model: string;
    year?: string;
    seatsCount?: number;
    tariffs: TariffCreateFields[]; // <-- here is another one special type
}

interface TariffCreateFields {
    days: number;
    price: number;
}
```



Also we need to define two functions: `toDb` to prevent some fields, f.e. `tariffs` be passed to model. And `fromDb` "translates" model to `Vehicle` entity, removes or converts some fields:

```typescript
const u = <T>(value: T | undefined | null): (T | undefined) => value === null ? undefined : value;

const toDb = (f: VehicleCreateForm): VehicleCreateFields => ({
    model: f.model,
    year: f.year,
    seatsCount: f.seatsCount,
});

const fromDb = (m: VehicleModel, tariffs: Tariff[]): Vehicle => ({
    id: m.id,
    createdAt: m.createdAt,

    model: m.model,
    year: u(m.year),
    tariffs,
});
```



And, yaaa, we need one more extra type: `VehicleCreateFields`. Fields we are passing to model constructor.

```typescript
interface VehicleCreateFields {
    model: string;
    year?: string;
    seatsCount?: number;
}
```



Seems like here is done. 

Also we need to define `tariff` namespace similar to `vehicle`. All types and will be duplicated too: `TariffSchema`, `TariffModel`, `TariffCreateForm`, `TariffCreateDocument`. 



And this will happen for every new entity. In current case I can avoid creating `VehicleUpdateFields` type and use `VehicleCreateFields` for creating and updating. There could be also `VehicleUpdateDocument` and etc.


How can I reduce the amount of code duplications. How you deal with typescript/mongoose? 

Ofcouse I can extract common fields to "common chunck" interfaces, but I event don't know how to name them. I tried to use interface inherence, end up with messy hell of small types. Like:
```typescript
interface Entity {
    id: string;
    createdAt: Date;
}

interface VehicleOwnFields {
    model: string;
    year?: string;
    seatsCount?: number;
}

interface VehicleOwnFields {
    model: string;
    year?: string;
    seatsCount?: number;
}

interface VehicleExternalFields {
    tariffs: Tariff[];
}

type Vehicle = Entity && VehicleOwnFields && VehicleExternalFields;
// and so on... 
```
I think this is not the best solution.  

Also there can be cases when fields has different types in Entity and in Schema. F.e. I can decide to serialize `Tariff[]` to JSON and store as string field in `VehicleModel`. 


I think this is general problem for typescript and ORMs. 
If you know good articles where this problem is described, please give me link.