Becoming More Proficient on the Back-End (Part 2)
In case you missed it, check out Part 1 where I talk about my Q3 goals and explain how this series works.
Now, here’s Part 2!
Progress Update
During the week of July 21st, my goal was to complete the following:
- Modern TS Course
- Validation and Error Handling — 30 mins
- Data Persistence — PostgresSQL & TypeORM — 1 hour
✅ Completed Goal
I put in extra hours this weekend to get back on track for my plan so I completed both of the course sections.
🤔 What did I do?
I learned about validation, error handling and data persistence. I also integrated TypeORM and a PostgresSQL database.
💡 Key Learnings
Again, I have experience in these areas but here are some things that stuck out this time around:
- data transfer objects (DTOs)
A data transfer object is essentially the definition of a piece of data, which may be used across multiple layers of your application.
For instance, in the Nest.js application I’m building, there is a /tasks controller. On the controller, we have a POST endpoint on the root where you can create a task. The controller utilizes the service (where the logic actually lives). Since these both accept the same parameter when creating a task, this is perfect use case for a DTO. We create a create-task.dto.ts which looks like this:
export class CreateTaskDto { title: string; description: string; }
Then, import it wherever we need it within the application. Viola!
One comment the instructor made was to define it using ES6 Classes instead of a TypeScript interface, so the shape would be preserved after the TS complication to JS. This also makes code easier to maintain in the future.
- Nest.js pipes
In terms of validation, Nest.js provides classes called ValidationPipes
and ParseIntPipe
. I like to think of these pipes as paths we send data on. When we use this on handlers or methods within our controllers, Nest.js will "pipe the data" into these validation pipes and check them against the DTOs we provided. In this course, we use them in tandem with a package called class-validator
which provides decorators for you're classes. Here's our updated create-task.dto.ts
with the validators.
import { IsNotEmpty } from 'class-validator'; export class CreateTaskDto { @IsNotEmpty() title: string; @IsNotEmpty() description: string; }
As you can see we added the IsNotEmpty
decorators to make sure title
and description
are never empty strings.
Then in our controller, we can tell Nest.js to use a Validation Pipe like so:
@Post() @UsePipes(ValidationPipe) createTask(@Body() createTaskDto: CreateTaskDto): Promise<Task> { return this.tasksService.createTask(createTaskDto); }
We’re basically saying, “Hey Nest! on this POST
handler, I want you to use the ValidationPipe
and validate the Body
of the request again the CreateTaskDto
. Sound good? Thanks!"
The ParseIntPipe
is used to convert a string
to a number
so pipes can also be used for data transformation.
⁉️ Challenges
Below are some things that were difficult for me and that I’m still wrapping my head around.
- repositories
After adding TypeORM and PostgresSQL, we added a “repository” layer. The documentation states:
Repository is supposed to work with your entity objects. Find entities, insert, update, delete, etc.
As outline in the docs, the next step is to create a Task
entity (which essentially is a table in the database for tasks
) and then create a task.repository.ts
file. In this file, two methods were added: getTasks
and createTask
. I believe the reason behind this is because the methods involved more complex logic related to using the queryBuilder
provided by TypeORM and the purpose was to separate logic.
That’s all for my update! Thanks for reading and catch ya next week! 👋