Combining decorators for easy data manipulation

Romain Kelifa
5 min readMar 31, 2020
Photo by Samuel Sianipar on Unsplash

Hello my fellow confined nerds lol
More seriously I wish you all to be safe and sound 😷

I made a few experiments for a couple of days and I wanted to lay down my thoughts on data manipulation with TypeScript decorators in Nest.js.

Concepts

Whether writing websites, softwares, apps or anything involving sending data back and forth there’s always this chore to serialize/deserialize from one format to another, whether it be :

  • JS object to/from JSON
    yeah, quite straightforward
  • plain JS object to/from typed JS class object
    yeah, cumbersome heh
  • Java POJO to/from JSON
    I see Hibernate/Jackson users smile at the back
  • and so on : AMF, Protobuf, XML, YML, etc…

Now aside from serialization/deserialization, there’s also this fact that some data can cross boundaries or not.

Boundaries can be anything like your backend as opposed to your frontend for example. So there’s also this one another chore like sanitization and hydration :

  • sanitization : best example here would be the password from your user which is typically the data that you want to strip away from being inadvertently sent to your frontend.
  • hydration : reverse process, when sometimes you receive user data from the frontend and want to feed its missing properties from database to carry out some more logic.

And so what ? So it’s a lot of boilerplate.
And me dumb lazy ass hates dat 🙄

Let’s code

I’m happy to share some experience with Nest.js, class-validator and class-transformer which makes it way more easier and less verbose.

Requirements

This sample assumes that you’re already a bit familiar with these librairies :

  • typescript ^3.7.4
  • nest.js^7.0.0
  • typeorm ^0.2.24
  • class-transformer ^0.2.3
  • class-validator ^0.12.0-rc.0
  • moment ^2.24.0
  • chance ^1.1.4

Architecture

In a nutshell, request/response can be seen as a pipe flow :

architecture with Nest.js (simplified)

Entity

The whole trick lies here for the most part. By taking advantages of typeorm, class-validator and class-transformer to accurately define what our data will look like in different contexts :

  • as a database representation
  • as a typed JS class object
  • as a plain JS object
notes.entity.ts

available as a gist here

constants.ts

available as a gist here

Here I will only focuson, user and owner :

  • on : @Column with transformer handles the conversion between database to JS representations, while both@Transform handle the conversion between plain JS object and typed JS object (notice the toClassOnly and toPlainOnly). It is customized to handle conversion between a moment object and a string representation while keeping all the informations (including milliseconds, how nice is that !).
  • user : this illustrates when a data is meant to exist solely in backend, as it will be excluded when transformed into a plain JS object with @Exclude. It will just allow for conveniently loading relation (joint table in database) to retrieve the User owning the Note with @ManyToOne. The key for this joint is owner as specified with @JoinColumn.
  • owner : this data is the key to the User id whose this Note belongs to. It will be exposed when transformed into a plain JS object with @Expose.

The other properties are pretty much self-explanatory, don’t hesitate to ask in the comments for more information or directly check out the libraries API.

Interceptor

IOInterceptor will automate transforming JS class object to plain JS object with classToPlain on each output, which of course will also sanitize the data marked with @Expose and @Exclude as specified earlier too !

io.interceptor.ts

available as a gist here

Controller

By adding the previously created IOInterceptor to the controller itself, we ensures that every data send back to the outer world will first be transformed into a plain JS object.

Nest.js will then turn it into JSON at the very end

notes.controller.ts

available as a gist here

Seeds

In order to test our sample app in real conditions, we first want to be able to seed our test database with some entries.

seed.ts

available as a gist here

Here we’re creating random data with chance to allow for some entropy. Notes are also randomly allocated to some previously created users.

⚠️ I got befuddled at first when implementing because I forgot that a moment object can automatically be created from a valid date string, hence moment.utc(chance.date({ year: 1983 }).format(TIME) (while casting it to unknown and moment.Moment is just a means to let TypeScript compiler allows it).

Thanks to cojack for pointing this out in this issue.

Integration test

Now that everything is ready, let’s test it :

app.module.test.ts

available as a gist here

As you can see, every time we run the test we previously seed our database with random data and ensures that these are the exact same ones we receive from the backend afterwards.

I intentionally skipped some files to focus on the main points but you can find the whole repository here :

As you can guess, from that on data validation could also be easily implemented with Nest.js Pipe, check out my previous article on this :

Wish you all a good day 🖐

As always,
don’t hesitate to comment 💬
or show some support 👏

--

--