Simple way to handle monetary data in MongoDB in a NestJs application
While building my backend NestJs application, I wanted to store some financial data. The first question I ran into was what schema Type should be for storing data where I want to maintain the data precision.
Decimal128 came out to be the prescribed official candidate to store the financial data.
Here’s my initial schema implementation:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { Decimal128, HydratedDocument, Types } from 'mongoose';
export type UserDocument = HydratedDocument<User>;
@Schema({
toJSON: {
getters: true,
},
})
export class User {
@Prop()
firstName: string;
@Prop()
lastName: string;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
})
total_invested: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
})
target_returns: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
})
target_profit_percent: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
})
actual_returns: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
})
actual_profit_percent: Decimal128;
}
export const UserSchema = SchemaFactory.createForClass(User);
There was one small issue that I soon ran into. The database would return the Decimal128 column data in this format:
{
"_id": "64d50a0bf9e3e104920dec6a",
"firstName": "shiva",
"lastName": "chaturvedi",
"total_invested": {
"$numberDecimal": "0"
},
"target_returns": {
"$numberDecimal": "0"
},
"target_profit_percent": {
"$numberDecimal": "0"
},
"actual_returns": {
"$numberDecimal": "0"
},
"actual_profit_percent": {
"$numberDecimal": "0"
},
"__v": 0,
"id": "64d50a0bf9e3e104920dec6a"
}
Whereas I wanted the database to return transformed results or, simply put, data in a good old-fashioned key-value pair format.
Solution: Use Getters
Implementing getters
your schema props is very easy.
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { Decimal128, HydratedDocument, Types } from 'mongoose';
export type UserDocument = HydratedDocument<User>;
@Schema()
export class User {
@Prop()
firstName: string;
@Prop()
lastName: string;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
get: (v: Types.Decimal128) => v.toString(),
})
total_invested: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
get: (v: Types.Decimal128) => v.toString(),
})
target_returns: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
get: (v: Types.Decimal128) => v.toString(),
})
target_profit_percent: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
get: (v: Types.Decimal128) => v.toString(),
})
actual_returns: Decimal128;
@Prop({
default: new Types.Decimal128('0'),
type: Types.Decimal128,
get: (v: Types.Decimal128) => v.toString(),
})
actual_profit_percent: Decimal128;
}
export const UserSchema = SchemaFactory.createForClass(User);
Now, let’s test the API again.
“What am I missing?” I wondered.
After a short walk on the internet, a good samaritan pointed out that we also need to set getters
flag as true
on the @Schema
decorator as illustrated below:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import mongoose, { Decimal128, HydratedDocument, Types } from 'mongoose';
export type UserDocument = HydratedDocument<User>;
@Schema({
toJSON: {
getters: true,
},
})
This enables the props to execute/use their getter functions.
Let’s test again!
Hallelujah ? ?
Want to know more about Decimal128 Type? Read this beautiful article written by Ken W. Alger on MongoDB official website.
P.S.: If you feel something can be improved or lacks proper explanation, drop us a note in the comment section below, and we’ll try to