The fact is that MongoDB is a NoSQL database. It means that it is non-relational, among other things. To implement a kind of relations between documents, we use references by IDs or embed documents directly.
One-To-One (1:1)
// src/models/address/address.interface.ts
export interface IAddress {
city: string;
street: string;
}
// src/models/address/address.model.ts
import * as mongoose from 'mongoose';
import { IAddress } from './address.interface';
export const addressSchema = new mongoose.Schema<IAddress>({
city: String,
street: String,
})
export const addressModel = mongoose.model<IAddress & mongoose.Document>('Address', addressSchema);
export default addressModel;
// src/models/users/user.interface.ts
import { IAddress } from '../address/address.interface';
interface IUser {
_id: string;
firstName: string;
lastName: string;
fullName: string;
email: string;
password: string;
address?: IAddress;
}
export default IUser;
// src/models/users/user.model.ts
import * as mongoose from 'mongoose';
import IUser from './user.interface';
import { addressSchema } from '../address/address.model';
const userSchema = new mongoose.Schema<IUser>({
firstName: String,
lastName: String,
email: String,
password: String,
address: addressSchema,
});
const userModel = mongoose.model<IUser & mongoose.Document>('User', userSchema);
export default userModel;
The One-To-One relationship in this example means that a user has just one address and this address belongs to only one user. Since this is the case, it makes sense to embed the address straight into the user document.
One-To-Many (1:N)
// src/models/posts/post.interface.ts
import * as mongoose from 'mongoose';
export interface Post {
author: mongoose.Types.ObjectId | undefined;
content: string;
title: string;
}
// src/models/posts/post.model.ts
import * as mongoose from 'mongoose';
import {Post} from './post.interface';
export const postSchema = new mongoose.Schema<Post>({
author: {
ref: 'User',
type: mongoose.Schema.Types.ObjectId
},
content: String,
title: String,
});
// export const postSchema = new mongoose.Schema({
// author: {
// ref: 'User',
// type: mongoose.Schema.Types.ObjectId
// },
// content: String,
// title: String,
// });
// type UserSchema = mongoose.InferSchemaType<typeof postSchema>;
const postModel = mongoose.model<Post & mongoose.Document>('Post', postSchema);
export default postModel;
// src/dto/posts/post.dto.ts
import { IsString } from 'class-validator';
class CreatePostDto {
// @IsString()
// public author?: string;
@IsString()
public content?: string;
@IsString()
public title?: string;
}
export default CreatePostDto;
// src/service/posts/post.service.ts
export async function createPost(
request: RequestWithUser,
response: express.Response
) {
const postData: Post = request.body;
console.log('createPost: ', request.user);
console.log('createPost postData: ', postData);
const createdPost = new postModel({
...postData,
author: request.user._id,
});
const savedPost = await createdPost.save();
response.send(savedPost);
}
Populating the data with Mongoose
// src/service/posts/post.service.ts
export async function createPost(
request: RequestWithUser,
response: express.Response
) {
const postData: Post = request.body;
console.log('createPost: ', request.user);
console.log('createPost postData: ', postData);
const createdPost = new postModel({
...postData,
author: request.user._id,
});
const savedPost = await createdPost.save();
await savedPost.populate('author');
response.send(savedPost);
}
await savedPost.populate('author', 'email');
await savedPost.populate('author', '-password');
export async function getAllPosts(
request: express.Request,
response: express.Response
) {
const posts = await postModel.find().populate('author', '-password').exec();
response.send(posts);
}
The direction of the reference
retrieve all posts of the author
// src/exceptions/NotAuthorizedException.ts
import HttpException from './HttpException';
class NotAuthorizedException extends HttpException {
constructor() {
super(403, "You're not authorized");
}
}
export default NotAuthorizedException;
// src/service/posts/post.service.ts
import NotAuthorizedException from '../../exceptions/NotAuthorizedException';
//...
export async function getAllPostsOfUser(
request: RequestWithUser,
response: express.Response,
next: express.NextFunction
) {
const userId = request.params.id;
if (userId === request.user._id.toString()) {
const posts = await postModel.find({author: userId}).populate('author', '-password').exec();
response.send(posts);
return;
}
next(new NotAuthorizedException());
}
//...
// src/controller/posts/post.controller.ts
private initializeRoutes() {
// ...
this.router.get(`${this.path}/:id/posts`, authMiddleware as any, this.getAllPostsOfUser as any);
// ...
}
Many-To-Many (N:M)
Two-Way referencing
//src/models/posts/post.interface.ts
import * as mongoose from 'mongoose';
export interface Post {
authors: Array<mongoose.Types.ObjectId> | undefined;
content: string;
title: string;
}
// src/models/posts/post.model.ts
export const postSchema = new mongoose.Schema<Post>({
authors: [
{
ref: 'User',
type: mongoose.Schema.Types.ObjectId
}
],
content: String,
title: String,
});
// src/models/users/user.interface.ts
import { IAddress } from '../address/address.interface';
import * as mongoose from 'mongoose';
interface IUser {
_id: string;
firstName: string;
lastName: string;
fullName: string;
email: string;
password: string;
address?: IAddress;
posts: Array<mongoose.Types.ObjectId> | undefined
}
export default IUser;
// src/models/users/user.model.ts
import * as mongoose from 'mongoose';
import IUser from './user.interface';
import { addressSchema } from '../address/address.model';
const userSchema = new mongoose.Schema<IUser>({
firstName: String,
lastName: String,
email: String,
password: String,
address: addressSchema,
posts: [
{
ref: 'Post',
type: mongoose.Schema.Types.ObjectId
}
]
});
const userModel = mongoose.model<IUser & mongoose.Document>('User', userSchema);
export default userModel;
// src/service/posts/post.service.ts
export async function createPost(
request: RequestWithUser,
response: express.Response
) {
const postData: Post = request.body;
console.log('createPost: ', request.user);
console.log('createPost postData: ', postData);
const createdPost = new postModel({
...postData,
authors: [request.user._id],
});
const user = await userModel.findById(request.user._id);
if (!user) {
throw new NotAuthorizedException();
}
user.posts = [...user.posts!, createdPost._id];
await user.save();
const savedPost = await createdPost.save();
await savedPost.populate('author', '-password');
response.send(savedPost);
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGI2NTM4NDE1MGNlMzk5N2MyODYzY2EiLCJpYXQiOjE2ODk2NzQ2NzAsImV4cCI6MTY4OTY3ODI3MH0.09iOJoabtJMNGDrf6r1qcbYGkgukl1Q3Q-dwOmbdrKo
That' all.
ref: source