Define a database entity
Use npx vendure add
to easily add a new entity to a plugin.
Your plugin can define new database entities to model the data it needs to store. For instance, a product review plugin would need a way to store reviews. This would be done by defining a new database entity.
This example shows how new TypeORM database entities can be defined by plugins.
Create the entity class
import { DeepPartial } from '@vendure/common/lib/shared-types';
import { VendureEntity, Product, EntityId, ID } from '@vendure/core';
import { Column, Entity, ManyToOne } from 'typeorm';
@Entity()
class ProductReview extends VendureEntity {
constructor(input?: DeepPartial<ProductReview>) {
super(input);
}
@ManyToOne(type => Product)
product: Product;
@EntityId()
productId: ID;
@Column()
text: string;
@Column()
rating: number;
}
Any custom entities must extend the VendureEntity
class.
In this example, we are making use of the following TypeORM decorators:
@Entity()
- defines the entity as a TypeORM entity. This is required for all entities. It tells TypeORM to create a new table in the database for this entity.@Column()
- defines a column in the database table. The data type of the column is inferred from the TypeScript type of the property, but can be overridden by passing an options object to the decorator. The@Column()
also supports many other options for defining the column, such asnullable
,default
,unique
,primary
,enum
etc.@ManyToOne()
- defines a many-to-one relationship between this entity and another entity. In this case, manyProductReview
entities can be associated with a givenProduct
. There are other types of relations that can be defined - see the TypeORM relations docs.
There is an additional Vendure-specific decorator:
@EntityId()
marks a property as the ID of another entity. In this case, theproductId
property is the ID of theProduct
entity. The reason that we have a special decorator for this is that Vendure supports both numeric and string IDs, and the@EntityId()
decorator will automatically set the database column to be the correct type. ThisproductId
is not necessary, but it is a useful convention to allow access to the ID of the associated entity without having to perform a database join.
Register the entity
The new entity is then passed to the entities
array of the VendurePlugin metadata:
import { VendurePlugin } from '@vendure/core';
import { ProductReview } from './entities/product-review.entity';
@VendurePlugin({
entities: [ProductReview],
})
export class ReviewsPlugin {}
Once you have added a new entity to your plugin, and the plugin has been added to your VendureConfig plugins array, you must create a database migration to create the new table in the database.
Using the entity
The new entity can now be used in your plugin code. For example, you might want to create a new product review when a customer submits a review via the storefront:
import { Injectable } from '@nestjs/common';
import { RequestContext, Product, TransactionalConnection } from '@vendure/core';
import { ProductReview } from '../entities/product-review.entity';
@Injectable()
export class ReviewService {
constructor(private connection: TransactionalConnection) {}
async createReview(ctx: RequestContext, productId: string, rating: number, text: string) {
const product = await this.connection.getEntityOrThrow(ctx, Product, productId);
const review = new ProductReview({
product,
rating,
text,
});
return this.connection.getRepository(ctx, ProductReview).save(review);
}
}
Available entity decorators
In addition to the decorators described above, there are many other decorators provided by TypeORM. Some commonly used ones are:
There is also another Vendure-specific decorator for representing monetary values specifically:
@Money()
: This works together with theMoneyStrategy
to allow configurable control over how monetary values are stored in the database. For more information see the Money & Currency guide.
The full list of TypeORM decorators can be found in the TypeORM decorator reference
Corresponding GraphQL type
Once you have defined a new DB entity, it is likely that you want to expose it in your GraphQL API. Here's how to define a new type in your GraphQL API.
Supporting custom fields
From Vendure v2.2, it is possible to add support for custom fields to your custom entities. This is useful when you are defining a custom entity as part of a plugin which is intended to be used by other developers. For example, a plugin which defines a new entity for storing product reviews might want to allow the developer to add custom fields to the review entity.
First you need to update your entity class to implement the HasCustomFields
interface, and provide an empty class
which will be used to store the custom field values:
import {
DeepPartial,
HasCustomFields,
Product,
VendureEntity,
} from '@vendure/core';
import { Column, Entity, ManyToOne } from 'typeorm';
export class CustomProductReviewFields {}
@Entity()
export class ProductReview extends VendureEntity implements HasCustomFields {
constructor(input?: DeepPartial<ProductReview>) {
super(input);
}
@Column(type => CustomProductReviewFields)
customFields: CustomProductReviewFields;
@ManyToOne(type => Product)
product: Product;
@EntityId()
productId: ID;
@Column()
text: string;
@Column()
rating: number;
}
Now you'll be able to add custom fields to the ProductReview
entity via the VendureConfig:
import { VendureConfig } from '@vendure/core';
export const config: VendureConfig = {
// ...
customFields: {
ProductReview: [
{ name: 'reviewerName', type: 'string' },
{ name: 'reviewerLocation', type: 'string' },
],
},
};