The repository sample may be outlined as an abstraction over knowledge storage, permitting the decoupling of knowledge entry logic from the enterprise logic. The repository sample has plenty of advantages:
- It enforces the dependency inversion precept
- Since enterprise logic and knowledge entry logic are loosely coupled, they are often examined individually
- It helps with protecting the code structured and arranged
- It reduces duplication of code and enhances code maintainability
Like most design patterns, the repository sample is language agnostic. With that in thoughts, I’ll be exhibiting how one can implement the repository with TypeScript and Node.js. For the aim of demonstration, I’ll be utilizing Nest, a Node.js framework.
Getting began
Create a brand new Nest app
Like I mentioned earlier, we’ll be utilizing the Nest framework. So, let’s begin by making a recent Nest utility.
First, set up the Nest CLI in the event you don’t have already got it put in:
npm set up -g @nestjs/cli
As soon as put in, we are able to use the CLI to create a brand new Nest utility:
nest new nest-repository-pattern
To reveal the repository sample, we’ll be utilizing the idea of publish
. Let’s create the module and controller for it:
nest generate module publish nest generate controller publish --no-spec
These instructions will generate a publish.module.ts
file and a publish.controller.ts
file respectively inside a publish
listing throughout the src
listing.
Database setup
Subsequent, let’s arrange the database for our newly created Nest utility. I’ll be utilizing PostgreSQL, however you need to use any of the databases Knex helps. To work together with our database, we’ll be utilizing Objection.js, which is an ORM for Node.js constructed on high Knex. For this tutorial, we’ll be utilizing Nest Objection, a Nest module for Objection.
So, let’s set up all the mandatory dependencies:
npm set up knex objection @willsoto/nestjs-objection pg
As soon as put in, we are able to register the Nest Objection module contained in the imports
array of src/app.module.ts
and go alongside our database particulars:
// src/app.module.ts import { Module } from '@nestjs/frequent'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ObjectionModule } from '@willsoto/nestjs-objection'; @Module({ imports: [ ObjectionModule.register({ config: { client: 'pg', useNullAsDefault: true, connection: { host: '127.0.0.1', port: 5432, user: 'postgres', password: '', database: 'nest-repository-pattern', }, }, }), ], controllers: [AppController], suppliers: [AppService], }) export class AppModule {}
Mannequin and migration
To reveal the repository sample, we’ll be utilizing the idea of publish. So let’s create a Put up
mannequin and the corresponding migration to create a posts
desk.
Let’s begin with the mannequin. Contained in the publish
listing, create a brand new publish.mannequin.ts
file and paste the next code in it:
// src/publish/publish.mannequin.ts import { Mannequin } from 'objection'; export default class Put up extends Mannequin { static tableName="posts"; id: quantity; title: string; content material: string; }
The Put up
mannequin extends the bottom mannequin from Objection. Then we outline the title of the desk that this mannequin will use. Lastly, we outline the columns that the desk can have and their varieties.
Subsequent, let’s register the mannequin with the PostModule
by updating the module as beneath:
// src/publish/publish.module.ts import { Module } from '@nestjs/frequent'; import { ObjectionModule } from '@willsoto/nestjs-objection'; import { PostController } from './publish.controller'; import Put up from './publish.mannequin'; @Module({ imports: [ObjectionModule.forFeature([Post])], controllers: [PostController], }) export class PostModule {}
Let’s create the migration for the posts
desk. Earlier than we are able to create migrations, our challenge must have a knexfile
, and we are able to create the file by working the command beneath:
npx knex init -x ts
By default the init
command will create a knexfile.js
, however since we’re working with TypeScript, passing -x ts
will instruct the init
command to create a knexfile.ts
as a substitute. The file will created within the root of the challenge. Then, we change it content material with the next:
// knexfile.ts import sort { Knex } from 'knex'; const config: { [key: string]: Knex.Config } = { improvement: { shopper: 'pg', connection: { host: '127.0.0.1', port: 5432, consumer: 'postgres', password: '', database: 'nest-repository-pattern', }, migrations: { listing: './src/database/migrations', }, }, }; module.exports = config;
Ideally, you may need to have completely different configurations for various environments (improvement, staging, manufacturing, and so on.), however for the aim of this tutorial, I’ve solely added the configuration for the event setting. Along with the database config particulars, we additionally specified the listing the place migrations will reside.
Now, we are able to create the migration for the posts
desk:
npx knex migrate:make create_posts_table
As laid out in knexfile.js
, the migration can be created inside src/database/migrations
. Open it and replace it as beneath:
// src/database/migrations/TIMESTAMP_create_posts_table.ts import { Knex } from 'knex'; export async operate up(knex: Knex): Promise<void> { return knex.schema.createTable('posts', operate (desk) { desk.increments('id'); desk.string('title').notNullable(); desk.textual content('content material').notNullable(); }); } export async operate down(knex: Knex): Promise<void> { return knex.schema.dropTable('posts'); }
Within the up
operate, we’re making a posts
desk in our database with three columns: id
, title
, and content material
. The up
operate can be executed after we run the migration. Then contained in the down
operate, we’re merely dropping the posts
desk which may have been created. The down
operate can be executed after we roll again the migration.
Lastly, let’s run the migration:
npx knex migrate:up
Implementing the repository sample
Now, let’s get to the meat of this tutorial. The repository sample makes use of the idea of contracts (interface) and concrete implementations. Mainly, we outline contracts/interfaces that we’d desire a concrete implementation (class) to stick to.
Creating the repository contract
Having mentioned that, let’s create the publish contract/interface. Inside src
, create a brand new repositories
listing. That is the place we’ll retailer all our repositories. Contained in the newly created listing, create a PostRepositoryInterface.ts
file with the next content material:
// src/repositories/PostRepositoryInterface.ts import Put up from '../publish/publish.mannequin'; export default interface PostRepositoryInterface { all(): Promise<Put up[]>; discover(id: quantity): Promise<Put up>; create(knowledge: object): Promise<Put up>; }
That is the contract we wish all our publish concrete implementation to stick to. To maintain issues easy and simple, I’ve solely added three strategies.
Creating the concrete implementation
Subsequent, let’s create the concrete implementation. Since our utility present makes use of Knex to work together with the database, this would be the Knex implementation. Nonetheless contained in the repositories
listing, create a brand new KnexPostRepository.ts
file with the next content material:
// src/repositories/KnexPostRepository.ts import { Inject } from '@nestjs/frequent'; import Put up from 'src/publish/publish.mannequin'; import PostRepositoryInterface from './PostRepositoryInterface'; export default class KnexPostRepository implements PostRepositoryInterface { constructor(@Inject(Put up) personal readonly postModel: typeof Put up) {} async all(): Promise<Put up[]> { return this.postModel.question(); } async discover(id: quantity): Promise<Put up> { return this.postModel.question().the place('id', id).first(); } async create(knowledge: object): Promise<Put up> { return this.postModel.question().insert(knowledge); } }
The KnexPostRepository
class implements the PostRepositoryInterface
we created earlier, and so it due to this fact should adhere to the phrases of the contract; that’s, implement these strategies outlined within the interface. Inside the category constructor, we inject the Put up
mannequin into the category. Since we now have entry to the Put up
mannequin, we are able to use it to carry out the mandatory operations within the respective strategies.
Utilizing the repository
Now, to make use of the KnexPostRepository
we simply created, we have to first register with the Nest IoC container. We will try this by including it to the suppliers
array of the PostModule
:
// src/publish/publish.module.ts ... import KnexPostRepository from 'src/repositories/KnexPostRepository'; @Module({ ... suppliers: [ { provide: 'PostRepository', useClass: KnexPostRepository }, ], ... }) export class PostModule {}
Contained in the suppliers
array, we’re saying, “Hey, Nest, we wish the PostRepository
token to resolve to the KnexPostRepository
class.” In so doing, every time we inject PostRepository
(extra on this shortly), we are going to get an occasion of KnexPostRepository
.
Now, let’s truly make use of the repository. Replace the PostController
:
// src/publish/publish.controller.ts import { Controller, Get, Inject } from '@nestjs/frequent'; import PostRepositoryInterface from 'src/repositories/PostRepositoryInterface'; import Put up from './publish.mannequin'; @Controller('publish') export class PostController { constructor( @Inject('PostRepository') personal readonly postRepository: PostRepositoryInterface, ) {} @Get() async findAll() { return this.postRepository.all(); } }
The magic occurs contained in the constructor. Bear in mind the PostRepository
token from above? We inject it into the controller by way of its constructor, and in consequence the postRepository
property can be an occasion of KnexPostRepository
as defined above. Then we are able to conveniently use any of the strategies outlined within the repository.
That’s how one can use the repository. If down the road in the midst of the challenge we determine to change knowledge entry layer to one thing like Prisma, we’ll simply must create a PrismaPostRepository
class that implements the PostRepositoryInterface
:
// src/repositories/PrismaPostRepository.ts import PostRepositoryInterface from './PostRepositoryInterface'; export default class PrismaPostRepository implements PostRepositoryInterface { async all(): Promise<Put up[]> { // Prisma logic } async discover(id: quantity): Promise<Put up> { // Prisma logic } async create(knowledge: object): Promise<Put up> { // Prisma logic } }
Then merely register in with the Nest IoC container:
// src/publish/publish.module.ts ... import KnexPostRepository from 'src/repositories/KnexPostRepository'; @Module({ ... suppliers: [ // { provide: 'PostRepository', useClass: KnexPostRepository }, { provide: 'PostRepository', useClass: PrismaPostRepository }, ], ... }) export class PostModule {}
The controller code will principally stay the identical.
Refactoring the repository contract to make use of generics
Because it stands, we’ve got efficiently applied the repository sample and we are able to merely name it a day at this level. You’ll discover one thing although: PostRepositoryInterface
is tightly coupled with the Put up
mannequin, which isn’t an issue, per se, however think about we need to add commenting performance to our utility. We would lean in the direction of making a CommentRepositoryInterface
that can have the identical strategies and construction as PostRepositoryInterface
. Then, we’ll create a KnexCommentRepository
that can implement CommentRepositoryInterface
.
You’ll be able to instantly see the sample of code duplication as a result of PostRepositoryInterface
and CommentRepositoryInterface
are principally the identical with simply completely different fashions. So we’re going to refactor the interface such that it may be reusable with any fashions.
We’d like a option to go the mannequin to the interface. Fortunately for us, we are able to simply obtain that utilizing TypeScript generics.
We’re going to rename PostRepositoryInterface
to RepositoryInterface
and replace the code:
// src/repositories/RepositoryInterface.ts export default interface RepositoryInterface<T> { all(): Promise<T[]>; discover(id: quantity): Promise<T>; create(knowledge: object): Promise<T>; }
Right here, T
would be the mannequin that the concrete implementation is for. Any class that desires to utilize this interface should go to it the mannequin to totally adhere to the contract.
Now, we are able to make a slight adjustment to KnexPostRepository
by passing the Put up
mannequin to the RepositoryInterface
:
// src/repositories/KnexPostRepository.ts import RepositoryInterface from './RepositoryInterface'; export default class KnexPostRepository implements RepositoryInterface<Put up> { // remainder of the code stay the identical }
Then, KnexCommentRepository
can appear like this:
// src/repositories/KnexCommentRepository.ts import RepositoryInterface from './RepositoryInterface'; export default class KnexCommentRepository implements RepositoryInterface<Remark> { // strategies implementation for commenting performance }
Conclusion
On this tutorial, we discovered concerning the repository sample, a few of its advantages, and how one can implement the repository sample with TypeScript and Node.js. Additionally, we noticed how one can scale back code duplication utilizing TypeScript generics.
You will get the entire supply code for our demo from this GitHub repository.
200’s solely Monitor failed and gradual community requests in manufacturing
Deploying a Node-based internet app or web site is the simple half. Ensuring your Node occasion continues to serve assets to your app is the place issues get harder. When you’re involved in making certain requests to the backend or third social gathering providers are profitable, attempt LogRocket. https://logrocket.com/signup/
LogRocket is sort of a DVR for internet and cell apps, recording actually the whole lot that occurs whereas a consumer interacts along with your app. As a substitute of guessing why issues occur, you possibly can combination and report on problematic community requests to rapidly perceive the foundation trigger.
LogRocket devices your app to document baseline efficiency timings corresponding to web page load time, time to first byte, gradual community requests, and in addition logs Redux, NgRx, and Vuex actions/state. Begin monitoring at no cost.