Embedded
You can declare a property as embedded to embed a document inside another document.
@Definition()
export class Book {
@Id()
id: ObjectId;
@Property()
name: string;
}
@Definition()
export class Author {
@Id()
id: ObjectId;
@Property()
name: string;
@Embedded(() => Book)
books: Book[];
}
You should not include embedded definitions in the definitions
array of the DryerModule.register
method.
// app.module.ts
@Module({
imports: [
// will be other imports as well
DryerModule.register({
definitions: [
{
definition: Author,
embeddedConfigs: [
{
property: 'books',
allowedApis: ['create', 'update', 'remove', 'findOne', 'findAll'],
}
],
}
],
}),
],
})
export class AppModule {}
Generated APIs
Create API
You will be able to create an author with books.
mutation {
createAuthor(input: {
name: "Joanne Rowling"
books: [
{ name: "The Philosopher's Stone" }
{ name: "The Chamber of Secrets" }
]
}) {
id
name
books {
id
name
}
}
}
Update API
You will be able to update author and books as well.
mutation {
updateAuthor(input: {
id: "000000000000000000000000",
name: "J. K. Rowling"
books: [
{
id: "111111111111111111111111",
name: "Harry Potter and the Philosopher's Stone"
}
]
}) {
id
name
books {
id
name
}
}
}
If there are more books in the database than the ones you are sending, the extra books will be removed. To avoid this behavior you can include all the books in the update or use the updateAuthorBooks
API.
Create embedded API
mutation {
createAuthorBooks(
authorId: "000000000000000000000000"
inputs: [
{ name: "The Prisoner of Azkaban" }
{ name: "The Goblet of Fire" }
]
) {
id
name
}
}
Only newly created books will be returned.
Update embedded API
mutation {
updateAuthorBooks(
authorId: "000000000000000000000000"
inputs: [
{ id: "111111111111111111111111", name: "Harry Potter and the Prisoner of Azkaban" }
{ id: "222222222222222222222222", name: "Harry Potter and the Goblet of Fire" }
]
) {
id
name
}
}
Only books with the listed IDs will be returned, other books will remain unchanged but won't be included in the response.
Remove embedded API
mutation {
removeAuthorBooks(
authorId: "000000000000000000000000"
ids: [
"111111111111111111111111",
"222222222222222222222222"
]
) {
success
}
}
Find one embedded API
query {
authorBook(
authorId: "000000000000000000000000"
id: "111111111111111111111111"
) {
id
name
}
}
Find all embedded API
query {
authorBooks(
authorId: "000000000000000000000000"
) {
id
name
}
}
Object embedded
You can also embed an object. There will be no embedded APIs for this case, but you will be able to create and update the object from the parent document.
@Definition()
class Brand {
@Property()
name: string;
}
@Definition()
export class Car {
@Id()
id: ObjectId;
@Property()
name: string;
@Embedded(() => Brand)
brand: Brand;
}
You can disable _id for the embedded object by passing schemaOptions: { _id: false }
to the @Definition
decorator.
@Definition({ schemaOptions: { _id: false } })
export class Money {
@Property({ type: () => graphql.GraphQLInt })
amount: number;
@Property()
currency: string;
}
Multi-level Embedded
You can embed documents inside other embedded documents but the embedded APIs will only be generated for the first level. As on the example below, Author will have embedded Books and Books will have embedded Reviews.
@Definition()
export class Review {
@Id()
id: ObjectId;
@Property()
content: string;
}
@Definition()
export class Book {
@Id()
id: ObjectId;
@Property()
name: string;
@Embedded(() => Review)
reviews: Review[];
}
@Definition()
export class Author {
@Id()
id: ObjectId;
@Property()
name: string;
@Embedded(() => Book)
books: Book[];
}
Authentication
You should read the Authentication section before this one.
You can add any decorators for embedded APIs not just Authentication related decorators.
@Injectable()
export class MustBeUser implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
return (
GqlExecutionContext.create(context)
.getContext()
.req.header('user-role') === 'user'
);
}
}
@Definition()
export class Author {
@Embedded(() => Book)
books: Book[];
}
@Module({
imports: [
// will be other imports as well
DryerModule.register({
definitions: [
{
definition: Author,
embeddedConfigs: [
{
allowedApis: ['create', 'update', 'remove', 'findOne', 'findAll'],
decorators: { remove: UseGuards(MustBeUser) },
property: 'books',
},
],
}
],
}),
],
})
export class AppModule {}
The example above will add the @UseGuards(MustBeUser)
decorator to the removeAuthorBooks
resolver.
You can add decorators for other APIs as well. The decorators
object has the following structure:
decorators?: {
default?: MethodDecorator | MethodDecorator[];
write?: MethodDecorator | MethodDecorator[];
read?: MethodDecorator | MethodDecorator[];
findOne?: MethodDecorator | MethodDecorator[];
findAll?: MethodDecorator | MethodDecorator[];
remove?: MethodDecorator | MethodDecorator[];
update?: MethodDecorator | MethodDecorator[];
create?: MethodDecorator | MethodDecorator[];
};
You can use default
to add decorators to all APIs, write
to add decorators to all write APIs (create
, update
and remove
), read
to add decorators to all read APIs (findOne
and findAll
). There is a priority order for the fields. For example, remove
will have priority over write
and write
will have priority over default
.
Custom Schema
There is a onSchema
option that you can use to customize the schema of the embedded document.
import { UseGuards } from '@nestjs/common';
@Definition()
export class Author {
@Embedded(() => Book, {
onSubSchema: (schema) => {
schema.virtual('fullname').get(function() {
return this.name;
});
}
})
books: Book[];
}
Override Default Options
You can use overridePropertyOptions
to override the property options of the embedded property.
@Definition()
export class Car {
@Id()
id: ObjectId;
@Property()
name: string;
@Embedded(() => Brand, {
overridePropertyOptions: {
create: { nullable: false },
output: { nullable: false },
db: { required: true },
},
})
brand: Brand;
}
By default, the embedded property is nullable for create
and output
and not required for db
.
With the example above, the embedded property will be required for create
and output
and db
.