TypeORM Decorators
TypeORM is an Object Relational Mapping (ORM) library that simplifies database interactions in Node.js applications. It provides decorators to define and configure entities, columns, relationships, and more. These decorators help TypeORM map your JavaScript/TypeScript objects to database tables and columns.
Let’s start by looking at some commonly used TypeORM decorators:
1. @Entity
: The @Entity
decorator is used to mark a class as an entity. An entity represents a database table and its properties represent table columns. Here’s an example:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
In the above example, the User
class is marked as an entity using the @Entity
decorator. The id
, name
, and email
properties are marked as columns using the @Column
decorator.
2. @PrimaryGeneratedColumn
: The @PrimaryGeneratedColumn
decorator is used to mark a property as the primary key of an entity. It also specifies that the value of the column will be automatically generated. Here’s an example:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
In the above example, the id
property is marked as the primary key using the @PrimaryGeneratedColumn
decorator.
3. @Column
: The @Column
decorator is used to mark a property as a column in the entity’s table. It allows you to specify various options such as column type, length, default value, and more. Here’s an example:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column({ unique: true }) email: string; @Column({ default: false }) isAdmin: boolean; }
In the above example, the name
and email
properties are marked as columns using the @Column
decorator. The email
column is marked as unique, and the isAdmin
column has a default value of false
.
These are just a few examples of the decorators provided by TypeORM. You can explore more decorators such as @ManyToOne
, @OneToMany
, @ManyToMany
, and more to define relationships between entities.
Related Article: Working with GraphQL Enums: Values Explained
TypeORM Resolvers
TypeORM Resolvers are responsible for handling GraphQL queries and mutations for your entities. They define the operations that can be performed on your entities and interact with the TypeORM entities and repositories to retrieve and manipulate data.
To create a resolver for an entity, you typically follow these steps:
1. Import necessary dependencies:
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity';
2. Use the @Resolver
decorator to mark the class as a resolver:
@Resolver() export class UserResolver { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} }
3. Define GraphQL queries and mutations using decorators such as @Query
and @Mutation
:
@Resolver() export class UserResolver { // ... @Query(() => [User]) async users(): Promise<User[]> { return this.userRepository.find(); } @Mutation(() => User) async createUser(@Args('name') name: string, @Args('email') email: string): Promise<User> { const user = this.userRepository.create({ name, email }); return this.userRepository.save(user); } // ... }
In the above example, the users
query returns an array of User
entities by calling the find
method on the userRepository
. The createUser
mutation creates a new User
entity by calling the create
and save
methods on the userRepository
.
You can also include arguments in your queries and mutations using the @Args
decorator. The arguments are automatically injected into the resolver function.
TypeORM Entities
In TypeORM, entities represent database tables and their properties represent table columns. They are used to define the structure and behavior of the data stored in the database.
To create an entity in TypeORM, you typically follow these steps:
1. Create a new TypeScript class and mark it as an entity using the @Entity
decorator:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; }
In the above example, the User
class is marked as an entity using the @Entity
decorator. The id
, name
, and email
properties are marked as columns using the @Column
decorator.
2. Optionally, you can define relationships between entities using decorators such as @ManyToOne
, @OneToMany
, @ManyToMany
, and more. Here’s an example of a Post
entity with a many-to-one relationship with the User
entity:
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm'; import { User } from './user.entity'; @Entity() export class Post { @PrimaryGeneratedColumn() id: number; @Column() title: string; @Column() content: string; @ManyToOne(() => User, user => user.posts) author: User; }
In the above example, the Post
entity has a many-to-one relationship with the User
entity. The author
property is marked with the @ManyToOne
decorator, which specifies the target entity (User
) and the inverse side property (posts
) in the User
entity.
3. Repeat the above steps for other entities in your application.
Entities are the backbone of your TypeORM application. They define the structure of your database and provide a way to interact with the data stored in it.
TypeORM Mutations
In TypeORM with NestJS and GraphQL, mutations are used to modify data in the database. Mutations typically correspond to create, update, and delete operations.
To implement mutations in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Create a new resolver class and import necessary dependencies:
import { Resolver, Mutation, Args } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity';
2. Use the @Resolver
decorator to mark the class as a resolver:
@Resolver() export class UserResolver { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} // Mutations go here }
3. Define mutation methods using the @Mutation
decorator:
@Resolver() export class UserResolver { // ... @Mutation(() => User) async createUser(@Args('name') name: string, @Args('email') email: string): Promise<User> { const user = this.userRepository.create({ name, email }); return this.userRepository.save(user); } @Mutation(() => User) async updateUser(@Args('id') id: number, @Args('name') name: string, @Args('email') email: string): Promise<User> { const user = await this.userRepository.findOne(id); if (!user) { throw new Error('User not found'); } user.name = name; user.email = email; return this.userRepository.save(user); } @Mutation(() => Boolean) async deleteUser(@Args('id') id: number): Promise<boolean> { const user = await this.userRepository.findOne(id); if (!user) { throw new Error('User not found'); } await this.userRepository.remove(user); return true; } // ... }
In the above example, the createUser
mutation creates a new User
entity by calling the create
and save
methods on the userRepository
. The updateUser
mutation updates an existing User
entity by finding it using the findOne
method and modifying its properties. The deleteUser
mutation deletes a user by finding it using the findOne
method and removing it using the remove
method.
You can define mutations with any number of arguments and return types. The @Args
decorator is used to specify the input arguments of the mutation. The return type of the mutation is specified using the @Mutation
decorator.
Related Article: Working with FormData in GraphQL
TypeORM Subscriptions
TypeORM with NestJS and GraphQL supports real-time data updates using subscriptions. Subscriptions allow clients to subscribe to specific events or data changes and receive updates in real-time. This is useful for implementing features such as real-time notifications or chat applications.
To implement subscriptions in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Install the necessary dependencies:
npm install graphql-subscriptions apollo-server-express subscriptions-transport-ws
2. Create a new resolver class and import necessary dependencies:
import { Resolver, Subscription, Inject } from '@nestjs/graphql'; import { PubSubEngine } from 'graphql-subscriptions'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity';
3. Use the @Resolver
decorator to mark the class as a resolver:
@Resolver() export class UserResolver { constructor( @InjectRepository(User) private userRepository: Repository<User>, @Inject('PUB_SUB') private pubSub: PubSubEngine, ) {} // Subscriptions go here }
4. Define subscription methods using the @Subscription
decorator:
@Resolver() export class UserResolver { // ... @Subscription(() => User, { filter: (payload, variables) => { // Implement filtering logic here return payload.userId === variables.userId; }, }) userCreated() { return this.pubSub.asyncIterator('userCreated'); } // ... }
In the above example, the userCreated
subscription returns a user whenever a new user is created. The pubSub.asyncIterator
method is used to create a new subscription channel named 'userCreated'
. The filter
function is used to filter the subscriptions based on payload and variables.
5. Publish events to trigger subscriptions:
import { PubSub } from 'graphql-subscriptions'; // ... @Resolver() export class UserResolver { constructor( // ... @Inject('PUB_SUB') private pubSub: PubSubEngine, ) {} @Mutation(() => User) async createUser(@Args('name') name: string, @Args('email') email: string): Promise<User> { const user = this.userRepository.create({ name, email }); const savedUser = await this.userRepository.save(user); await this.pubSub.publish('userCreated', { userId: savedUser.id }); return savedUser; } // ... }
In the above example, the createUser
mutation publishes a 'userCreated'
event after saving the user. This triggers the userCreated
subscription and sends the new user to the subscribed clients.
Subscriptions allow you to implement real-time features in your TypeORM with NestJS and GraphQL application. You can create subscriptions for various events and data changes and use the pubSub
instance to publish events and trigger subscriptions.
TypeORM Directives
TypeORM with NestJS and GraphQL allows you to use directives to customize the behavior of your GraphQL schema and resolvers. Directives can be used to modify the execution of queries, mutations, and subscriptions based on certain conditions or requirements.
To use directives in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Install the necessary dependencies:
npm install type-graphql graphql-tools graphql
2. Create a new decorator to represent a directive:
import { SchemaDirectiveVisitor } from 'graphql-tools'; import { defaultFieldResolver, GraphQLField } from 'graphql'; export class UpperCaseDirective extends SchemaDirectiveVisitor { visitFieldDefinition(field: GraphQLField<any, any>) { const { resolve = defaultFieldResolver } = field; field.resolve = async function (...args) { const result = await resolve.apply(this, args); if (typeof result === 'string') { return result.toUpperCase(); } return result; }; } }
In the above example, a custom directive named @upper
is defined. This directive converts the value of a field to uppercase. It uses the SchemaDirectiveVisitor
class from graphql-tools
to visit and modify the field definition.
3. Apply the directive to a field in your entity:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { Field, ID, ObjectType } from 'type-graphql'; import { UpperCaseDirective } from './uppercase.directive'; @ObjectType() @Entity() export class User { @Field(() => ID) @PrimaryGeneratedColumn() id: number; @Field() @Column() name: string; @Field() @Column() email: string; @Field() @Column() @Directive('@upper') city: string; }
In the above example, the city
field is marked with the @upper
directive. This will cause the field resolver to apply the uppercase conversion defined in the UpperCaseDirective
.
4. Create a new entity resolver and import necessary dependencies:
import { Resolver, Query } from 'type-graphql'; import { getRepository } from 'typeorm'; import { User } from './user.entity'; @Resolver(() => User) export class UserResolver { private userRepository = getRepository(User); @Query(() => [User]) users(): Promise<User[]> { return this.userRepository.find(); } }
5. Register the directive and resolver in your application:
import { ApolloServer } from 'apollo-server-express'; import { buildSchema } from 'type-graphql'; import { UpperCaseDirective } from './uppercase.directive'; import { UserResolver } from './user.resolver'; async function bootstrap() { const schema = await buildSchema({ resolvers: [UserResolver], schemaDirectives: { upper: UpperCaseDirective, }, }); const server = new ApolloServer({ schema }); // ... }
In the above example, the UpperCaseDirective
is registered as a schema directive using the schemaDirectives
option in the buildSchema
function.
Directives provide a way to customize the behavior of your GraphQL schema and resolvers in TypeORM with NestJS and GraphQL. You can create custom directives to modify the execution of fields, arguments, or entire operations based on specific conditions or requirements.
TypeORM Interfaces
In TypeORM with NestJS and GraphQL, interfaces allow you to define common fields and functionality that can be shared across multiple entities. Interfaces provide a way to enforce a contract and ensure that entities implementing the interface have the required properties and methods.
To define and use interfaces in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Create a new TypeScript interface:
import { Field, ID, InterfaceType } from 'type-graphql'; @InterfaceType() export abstract class BaseEntity { @Field(() => ID) id: number; }
In the above example, the BaseEntity
interface is defined using the @InterfaceType
decorator from type-graphql
. The interface has a single field id
of type ID
.
2. Implement the interface in your entity:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; import { Field, ObjectType } from 'type-graphql'; import { BaseEntity } from './base.entity'; @ObjectType({ implements: BaseEntity }) @Entity() export class User implements BaseEntity { @Field() @PrimaryGeneratedColumn() id: number; @Field() @Column() name: string; @Field() @Column() email: string; }
In the above example, the User
entity implements the BaseEntity
interface using the implements
option in the @ObjectType
decorator. The id
property is defined in the User
entity to satisfy the requirements of the BaseEntity
interface.
3. Use the interface in your resolvers:
import { Resolver, Query } from 'type-graphql'; import { getRepository } from 'typeorm'; import { BaseEntity } from './base.entity'; @Resolver(() => BaseEntity) export class BaseEntityResolver { private baseEntityRepository = getRepository(BaseEntity); @Query(() => [BaseEntity]) baseEntities(): Promise<BaseEntity[]> { return this.baseEntityRepository.find(); } }
In the above example, the BaseEntity
interface is used as the return type in the resolver for the baseEntities
query.
Interfaces allow you to define common fields and functionality that can be shared across multiple entities in TypeORM with NestJS and GraphQL. They provide a way to enforce a contract and ensure that entities implementing the interface have the required properties and methods.
Related Article: Utilizing Debugger Inside GetInitialProps in Next.js
Handling Context in TypeORM
In TypeORM with NestJS and GraphQL, the context is an object that is shared across all resolvers within a single GraphQL operation. It allows you to pass data or services that are needed by multiple resolvers, such as authentication information, database connections, or other dependencies.
To handle the context in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Define the context type:
import { Request } from 'express'; export interface MyContext { req: Request; userId: number; }
In the above example, the MyContext
interface is defined with a req
property of type Request
from the express
library, and a userId
property of type number
.
2. Create a GraphQL context provider:
import { Injectable, ExecutionContext } from '@nestjs/common'; import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql'; import { AuthGuard } from '@nestjs/passport'; import { MyContext } from './my-context.interface'; @Injectable() export class MyAuthGuard extends AuthGuard('jwt') { getRequest(context: ExecutionContext): any { const ctx = GqlExecutionContext.create(context); return ctx.getContext<MyContext>().req; } }
In the above example, the MyAuthGuard
class extends the AuthGuard
class from @nestjs/passport
and overrides the getRequest
method to extract the Request
object from the GraphQL context.
3. Use the context provider in your resolvers:
import { Resolver, Query, Context } from '@nestjs/graphql'; import { MyContext } from './my-context.interface'; @Resolver() export class UserResolver { @Query(() => User) me(@Context() ctx: MyContext): Promise<User> { const userId = ctx.userId; // Query user data based on the userId } }
In the above example, the me
query resolver uses the @Context
decorator to inject the MyContext
object into the resolver function. The userId
property can then be accessed from the context to perform user-specific operations.
The context is a useful mechanism in TypeORM with NestJS and GraphQL that allows you to share data or services across multiple resolvers within a single GraphQL operation. It enables you to pass information or dependencies that are needed by multiple resolvers in an efficient and structured way.
TypeORM Pagination
Pagination is a common requirement in web applications to display large sets of data in smaller, more manageable chunks. TypeORM with NestJS and GraphQL provides built-in support for pagination using the PaginationType
from typeorm-pagination
.
To implement pagination in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Install the necessary dependencies:
npm install typeorm-pagination
2. Create a new resolver class and import necessary dependencies:
import { Resolver, Query, Args } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; import { PaginationArgs } from './pagination.args'; import { PaginationType } from 'typeorm-pagination';
3. Use the @Resolver
decorator to mark the class as a resolver:
@Resolver() export class UserResolver { constructor( @InjectRepository(User) private userRepository: Repository<User>, ) {} // Queries go here }
4. Define a query method with pagination arguments:
@Resolver() export class UserResolver { // ... @Query(() => PaginationType(User)) async users(@Args() args: PaginationArgs): Promise<PaginationType<User>> { return this.userRepository.paginate(args); } // ... }
In the above example, the users
query accepts pagination arguments using the @Args
decorator and returns a PaginationType<User>
. The userRepository.paginate
method is used to retrieve paginated results based on the provided arguments.
5. Create a new arguments class for pagination:
import { ArgsType, Field, Int } from '@nestjs/graphql'; @ArgsType() export class PaginationArgs { @Field(() => Int, { defaultValue: 1 }) page: number; @Field(() => Int, { defaultValue: 10 }) limit: number; }
In the above example, the PaginationArgs
class is created using the @ArgsType
decorator. It defines two fields: page
and limit
. The defaultValue
option is used to provide default values for these fields.
You can now use the users
query with pagination arguments to retrieve paginated results from the userRepository
.
Pagination is a useful feature in TypeORM with NestJS and GraphQL to efficiently retrieve and display large sets of data. It allows you to control the number of results per page and the current page, making it easier to navigate and manage large datasets.
TypeORM Batching
Batching is a technique used to optimize database queries by combining multiple individual queries into a single batch query. TypeORM with NestJS and GraphQL provides support for batching using the dataloader
library.
To implement batching in TypeORM with NestJS and GraphQL, you can follow these steps:
1. Install the necessary dependencies:
npm install dataloader
2. Create a new resolver class and import necessary dependencies:
import { Resolver, Query, Args } from '@nestjs/graphql'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from './user.entity'; import { Loader } from 'nestjs-dataloader';
3. Use the @Resolver
decorator to mark the class as a resolver:
@Resolver() export class UserResolver { constructor( @InjectRepository(User) private userRepository: Repository<User>, @Loader('UserLoader') private userLoader: DataLoader<number, User>, ) {} // Queries go here }
4. Define a query method to load users:
@Resolver() export class UserResolver { // ... @Query(() => User) async user(@Args('id') id: number): Promise<User> { return this.userLoader.load(id); } // ... }
In the above example, the user
query method uses the userLoader
to load a user by calling the load
method with the user’s id
. The userLoader
is injected using the @Loader
decorator and is configured with the key 'UserLoader'
.
5. Create a dataloader function to batch load users:
import DataLoader from 'dataloader'; import { User } from './user.entity'; export const createUserLoader = (userRepository: Repository<User>): DataLoader<number, User> => new DataLoader<number, User>(async (ids: number[]) => { const users = await userRepository.findByIds(ids); const userMap: Record<number, User> = {}; users.forEach((user) => { userMap[user.id] = user; }); return ids.map((id) => userMap[id]); });
In the above example, the createUserLoader
function creates a new DataLoader
instance and provides a batch loading function. The batch loading function retrieves users from the userRepository
using the findByIds
method and returns them in the same order as the provided ids
.
You can now use the user
query method to load users, and the dataloader will automatically batch and cache the database queries, resulting in improved performance.
Related Article: Using the JavaScript Fetch API for Data Retrieval
Additional Resources
– TypeORM Integration with NestJS for GraphQL
– TypeORM Entities and GraphQL
– Creating a Resolver in NestJS for GraphQL