Relationship in NoSQL database is not the same as traditional SQL database. That’s why you need to implement basic types of associations (such as One-to-One, One-to-Many, Many-to-Many) yourself. In this tutorial, I will show you 2 ways to make One-to-One Relationships, then we will implement Mongoose One-to-One relationship example in a Nodejs & MongoDB project.
Related Posts:
– MongoDB One-to-Many Relationship tutorial with Mongoose examples
– MongoDB Many-to-Many Relationship with Mongoose examples
– Node.js, Express & MongoDb: Build a CRUD Rest Api example
– Node.js + MongoDB: User Authentication & Authorization with JWT
Contents
Model One-to-One Relationships
Assume that we have 2 entity: Identifier and Customer. For example:
// Identifier
{
_id: "12345xyz",
cardCode: "BKD2019",
}
// Customer
{
_id: "cus123",
name: "bezkoder",
age: 29,
gender: "male"
}
We want to map Identifier and Customer relationship in that:
– One Customer has only one Identifier.
– One Identifier belongs to only one Customer.
This is called One-to-One Relationship.
Now let’s take a look at 2 ways to design schema for this kind of model.
Reference Data Models (Normalization)
In this model, an object A connects to the object B by reference to object B id or a unique identification field.
For example, Identifier has a customer_id
field which value is equal to Customer object’s unique _id
value.
// Identifier
{
_id: "12345xyz",
cardCode: "BKD2019",
customer_id: "cus123",
}
// Customer
{
_id: "cus123", // equal Identifier[customer_id]
name: "bezkoder",
age: 29,
gender: "male"
}
Embedded Data Models (Denormalization)
It’s easy to understand with ‘Embedded’ word. Instead of using a reference, Object A contains the whole object B, or object B is embedded inside object A.
You can see the example below, Identifier will have a nested field customer
.
// Identifier
{
_id: "12345xyz",
cardCode: "BKD2019",
customer: {
_id: "cus123", // Identifier[customer_id]
name: "bezkoder",
age: 29,
gender: "male"
}
}
You’ve known 2 ways to make One-to-One Relationships. Let’s implement each of them in a Node.js app using Mongoose. After that, I will tell you which model should be used for implementing One-to-One relationship between collections in MongoDb Database.
Mongoose One-to-One relationship example
Setup Nodejs App
First we need to install mongoose, so run the command:
npm install mongoose
Next, we create project structure like this:
src
models
Customer.js
Identifier.js
server.js
package.json
Open server.js, we import mongoose
to our app and connect to MongoDB database.
const mongoose = require("mongoose");
mongoose
.connect("mongodb://localhost/bezkoder_db", {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log("Successfully connect to MongoDB."))
.catch(err => console.error("Connection error", err));
The first step is done, we’re gonna create appropriate models and use mongoose
to interact with MongoDB database in two ways:
- Referencing
- Embedding
Mongoose One-to-One relationship: Referencing
1. Define One-to-One models using Mongoose
Let’s create 2 main model with mongoose.Schema()
construtor function.
In models/Customer.js, define Customer
with 3 fields: name
, age
, gender
.
const mongoose = require("mongoose");
const Customer = mongoose.model(
"Customer",
new mongoose.Schema({
name: String,
age: Number,
gender: String
})
);
module.exports = Customer;
Identifier object will have cardCode
field and a reference customer
.
So open models/Identifier.js, define Identifier
like this:
const mongoose = require("mongoose");
const Identifier = mongoose.model(
"Identifier",
new mongoose.Schema({
cardCode: String,
customer: {
type: mongoose.Schema.Types.ObjectId,
ref: "Customer"
}
})
);
module.exports = Identifier;
In the code above, we add customer
field, set its type
to ObjectId
and ref
to Customer
. What does it help?
Now if we save an Identifier to MongoDB database, a document will be added like this:
{
_id : ObjectId("5da000be062dc522eccaedeb"),
cardCode : "5DA000BC06",
customer : ObjectId("5da000bc062dc522eccaedea"),
__v : 0
}
Let’s test it, and I will show you how to get an Identifier object with full-fields Customer in this approach.
2. Test with MongoDB database
Open server.js, add the code below:
const Customer = require("./models/Customer");
const Identifier = require("./models/Identifier");
const createCustomer = function(name, age, gender) {
const customer = new Customer({
name,
age,
gender
});
return customer.save();
};
const createIdentifier = function(cardCode, customer) {
const identifier = new Identifier({
cardCode,
customer
});
return identifier.save();
};
createCustomer("bezkoder", 29, "male")
.then(customer => {
console.log("> Created new Customer\n", customer);
const customerId = customer._id.toString();
return createIdentifier(customerId.substring(0, 10).toUpperCase(), customerId);
})
.then(identifier => {
console.log("> Created new Identifier\n", identifier);
})
.catch(err => console.log(err));
Console shows the result.
> Created new Customer
{ _id: 5da135bd61a1dd3e9c2a6e81,
name: 'bezkoder',
age: 29,
gender: 'male',
__v: 0 }
> Created new Identifier
{ _id: 5da135bf61a1dd3e9c2a6e82,
cardCode: '5DA135BD61',
customer: 5da135bd61a1dd3e9c2a6e81,
__v: 0 }
We can check collections in the database.
As I’ve said before, the Identifier has only ObjectId
in customer
field.
The question is: How to get full items of customer
?
Oh yeah, we can reference documents in other collections using populate().
Let me show you how to do this.
const showAllIdentifier = async function() {
const identifiers = await Identifier.find().populate("customer");
console.log("> All Identifiers\n", identifiers);
};
The result looks like this.
> All Identifiers
[ { _id: 5da135bf61a1dd3e9c2a6e82,
cardCode: '5DA135BD61',
customer:
{ _id: 5da135bd61a1dd3e9c2a6e81,
name: 'bezkoder',
age: 29,
gender: 'male',
__v: 0 },
__v: 0 } ]
Mongoose Hide _id & __v in result
If you don’t want to get customer._id
& customer.__v
in the result, just add second parameters to populate()
function like this:
const identifiers = await Identifier.find()
.populate("customer", "-_id -__v");
Check the result:
> All Identifiers
[ { _id: 5da135bf61a1dd3e9c2a6e82,
cardCode: '5DA135BD61',
customer: { name: 'bezkoder', age: 29, gender: 'male' },
__v: 0 } ]
How about __v
in parent object?
We use select()
function to remove it.
const identifiers = await Identifier.find()
.populate("customer", "-_id -__v");
.select("-__v");
The result will be:
> All Identifiers
[ { _id: 5da135bf61a1dd3e9c2a6e82,
cardCode: '5DA135BD61',
customer: { name: 'bezkoder', age: 29, gender: 'male' } } ]
Mongoose One-to-One relationship: Embedding
1. Define One-to-One models using Mongoose
The way we define models for Embedded Documents will be different from Referenced Documents.
In models/Customer.js, we also define Customer
like code above, but also export CustomerSchema
.
const mongoose = require("mongoose");
const CustomerSchema = new mongoose.Schema({
name: String,
age: Number,
gender: String
});
const Customer = mongoose.model("Customer", CustomerSchema);
module.exports = { Customer, CustomerSchema };
Identifier object will still have cardCode
field & customer
field. But instead using a reference, we assign import and CustomerSchema
directly.
models/Identifier.js
const mongoose = require("mongoose");
const CustomerSchema = require("./Customer").CustomerSchema;
const Identifier = mongoose.model(
"Identifier",
new mongoose.Schema({
cardCode: String,
customer: CustomerSchema
})
);
module.exports = Identifier;
2. Test with MongoDB database
Open server.js, the definition of createCustomer
& createIdentifier
functions will be the same as Referencing.
It’s a little change in how we call createIdentifier()
. Instead passing customerId
, we use customer
object.
const Customer = require("./models/Customer").Customer;
const Identifier = require("./models/Identifier");
const createCustomer = function(name, age, gender) {
const customer = new Customer({
name,
age,
gender
});
return customer.save();
};
const createIdentifier = function(cardCode, customer) {
const identifier = new Identifier({
cardCode,
customer
});
return identifier.save();
};
createCustomer("bezkoder", 29, "male")
.then(customer => {
console.log("> Created new Customer\n", customer);
return createIdentifier(
customer._id.toString().substring(0, 10).toUpperCase(),
customer
);
})
.then(identifier => {
console.log("> Created new Identifier\n", identifier);
})
.catch(err => console.log(err));
Run the code, then check result in the console.
> Created new Customer
{ _id: 5da1406de666c118c89bba28,
name: 'bezkoder',
age: 29,
gender: 'male',
__v: 0 }
> Created new Identifier
{ _id: 5da1406fe666c118c89bba29,
cardCode: '5DA1406DE6',
customer:
{ _id: 5da1406de666c118c89bba28,
name: 'bezkoder',
age: 29,
gender: 'male',
__v: 0 },
__v: 0 }
Now look at our MongoDB database. Full Customer object is embedded in Identifier object now.
To get the items in identifiers
collections, just use find()
function.
const showAllIdentifier = async function() {
const identifiers = await Identifier.find();
console.log("> All Identifiers\n", identifiers);
};
And you can see all things you want in the result:
> All Identifiers
[ { _id: 5da1406fe666c118c89bba29,
cardCode: '5DA1406DE6',
customer:
{ _id: 5da1406de666c118c89bba28,
name: 'bezkoder',
age: 29,
gender: 'male',
__v: 0 },
__v: 0 } ]
Mongoose Hide _id & __v in result
With embedded documents, to hide _id
& __v
is easy with only one select()
function.
For example, if I want to remove Identifier __v
, also embedded document (Customer) fields such as customer._id
& customer.__v
, just write select()
with parameter like this.
const showAllIdentifier = async function() {
const identifiers = await Identifier.find()
.select("-__v -customer.__v -customer._id");
console.log("> All Identifiers\n", identifiers);
};
Now the result is pretty.
> All Identifiers
[ { _id: 5da1406fe666c118c89bba29,
cardCode: '5DA1406DE6',
customer: { name: 'bezkoder', age: 29, gender: 'male' } } ]
Referencing or Embedding for One-to-One Relationships
To make One-to-One Relationship between documents, we can reference or embed a document in the other. Which should be used in general?
Now remind what we’ve done and you can see that we have to use an additional function called populate()
after find()
function for Referenced Data Model.
Embedding stores related information directly inside the objects. So we only use find()
function to get everything. It gives us better performance with a single query. This Model also helps us update the embedded data in just one query using Dot Notation.
So the answer is that modeling One-to-One relationships with Embedded documents is the better choice.
Conclusion
Today we’ve learned two ways to implement One-to-One Relationships, then apply Mongoose One-to-One Relationships example in a Nodejs-MongoDB project. I’ve also told you which is the best approach for making MongoDB One-to-One Relationship: Embedding documents.
Happy learning! See you again.
Further Reading
Fullstack CRUD App:
– MEVN: Vue.js + Node.js + Express + MongoDB example
– MEAN:
Angular 8 + Node.js + Express + MongoDB example
Angular 10 + Node.js + Express + MongoDB example
Angular 11 + Node.js + Express + MongoDB example
Angular 12 + Node.js + Express + MongoDB example
Angular 13 + Node.js + Express + MongoDB example
Angular 14 + Node.js + Express + MongoDB example
Angular 15 + Node.js + Express + MongoDB example
Angular 16 + Node.js + Express + MongoDB example
– MERN: React + Node.js + Express + MongoDB example
Source Code
You can find the complete source code for this example on Github.
Thank you for this article. I do however have a question: We’re establishing a one-one connection, but only in the Identifier to customer direction. From what I understand this is unidirectional. If we find customers, we don’t know to which Identifiers they are attached to in your example. That’s correct?
If so, do we need to do all the exact same steps in an opposite way?
Hi Bez, you are such an amazing instructor, the article was very helpful for me! Thank you.
I have a question, that is can I update the customerId in Identifier table? If yes how do we perform that?
Excellent Tutorial! Thank you so much!
what if we update customer name? do we need to update data in identifier manualy too?
Hi, for both ways (referencing or embedding), we only need to update one field:
name
.very useful document
Thank you for your explanation.
Can you make many to many mongoose with node js?
Have a good day!
Hi Nabil, I will write mongoose many to many with Node.js soon.
Have a good day!
This is the MongoDB tutorial I need about One-to-One Association. Thank you so much!