Integrate databases with Node.js using raw drivers and ORMs.
In this chapter, we'll explore how to connect your Node.js application to MongoDB using Mongoose, the most popular ORM (Object-Relational Mapping) library for Node.js. We'll cover everything from setting up the connection to performing CRUD operations and managing relationships between documents.
const mongoose = require('mongoose');
// Connect to MongoDB
async function connectDB() {
try {
await mongoose.connect('mongodb://localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('Connected to MongoDB!');
} catch (error) {
console.error('Connection failed:', error);
}
}
connectDB();
Replace localhost:27017/myapp with your actual MongoDB connection string. If you're using a cloud service like MongoDB Atlas, use the appropriate connection URI.
Mongoose schemas define the structure of your data. Here's an example of a user schema:
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true,
select: false // Never return this field in queries
},
createdAt: {
type: Date,
default: Date.now
}
});
Let's look at how to perform basic CRUD operations using Mongoose:
// Create a new user
const createUser = async () => {
const user = new User({
name: 'John Doe',
email: 'john@example.com',
password: 'securepassword123'
});
try {
await user.save();
console.log('User created successfully!');
} catch (error) {
console.error('Error creating user:', error);
}
};
// Find all users
const findAllUsers = async () => {
try {
const users = await User.find().select('-password');
console.log(users);
} catch (error) {
console.error('Error fetching users:', error);
}
};
// Update a user
const updateUser = async () => {
try {
const updatedUser = await User.findByIdAndUpdate(
'user-id-here',
{ name: 'John Updated' },
{ new: true }
);
console.log(updatedUser);
} catch (error) {
console.error('Error updating user:', error);
}
};
// Delete a user
const deleteUser = async () => {
try {
await User.findByIdAndDelete('user-id-here');
console.log('User deleted successfully!');
} catch (error) {
console.error('Error deleting user:', error);
}
};
Mongoose supports relationships between documents through references. Here's an example of a post schema with a user reference:
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Comment'
}
]
});
To populate these references, use the populate() method:
const getPostWithAuthor = async () => {
try {
const post = await Post.findById('post-id-here')
.populate('author');
console.log(post);
} catch (error) {
console.error('Error fetching post:', error);
}
};
Mongoose provides built-in validation through schema definitions. Here are some common validators:
// Example schema with validation
const blogSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 5,
maxlength: 100
},
status: {
type: String,
enum: ['DRAFT', 'PUBLISHED'],
default: 'DRAFT'
}
});
Always handle potential errors when working with databases. Here's an example of error handling in a middleware:
// Middleware for error handling
function errorHandler(err, req, res, next) {
console.error('Error:', err);
if (err.name === 'ValidationError') {
return res.status(400).send({
success: false,
errors: Object.keys(err.errors).map(key => ({
field: key,
message: err.errors[key].message
}))
});
}
if (err.name === 'CastError') {
return res.status(400).send({
success: false,
message: 'Invalid ID format'
});
}
res.status(500).send({
success: false,
message: 'Internal server error'
});
}
In this chapter, we've covered the fundamentals of connecting to MongoDB using Mongoose in a Node.js application. You now know how to define schemas, perform CRUD operations, manage relationships between documents, and handle errors effectively.
Welcome to PostgreSQL with `pg` and Prisma! In this chapter, you'll learn how to work with one of the most popular relational databases using two powerful tools: the `pg` driver and the Prisma ORM. By the end of this section, you'll be able to confidently use both libraries to build robust applications.
The `pg` driver provides a low-level interface for connecting to PostgreSQL. Here's how to get started:
const { Pool } = require('pg');
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'mydb',
password: 'password',
port: 5432
});
pool.query('SELECT * FROM users', (err, res) => {
if (err) throw err;
console.log(res.rows);
});
Prisma is a modern ORM that simplifies database operations. It provides a type-safe and developer-friendly API.
// prisma.schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id
email String @unique
password String
}
Using the Prisma client:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function createUser(email, password) {
const user = await prisma.user.create({
data: {
email,
password
}
});
return user;
}
Migrations are essential for maintaining consistent database schemas across environments.
// Using pg
const { Client } = require('pg');
async function migrate() {
const client = new Client({
user: 'postgres',
host: 'localhost',
database: 'mydb',
password: 'password',
port: 5432
});
try {
await client.connect();
const res = await client.query(
'CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE)');
console.log('Table created');
} catch (err) {
console.error(err);
} finally {
await client.end();
}
}
// Using Prisma
$npx prisma migrate dev --name initial_migration
Validating data is crucial for maintaining data integrity. Use PostgreSQL constraints and Prisma's validation features.
// Database constraints in pg
ALTER TABLE users ADD CONSTRAINT unique_email UNIQUE (email);
// Validation with Prisma
const createUser = await prisma.user.create({
data: {
email,
password,
},
validate: true
});
Combining `pg` and Prisma can give you the best of both worlds. Use `pg` for raw database operations and Prisma for higher-level abstractions.
// Example integration
const { Pool } = require('pg');
import { PrismaClient } from '@prisma/client';
const pool = new Pool({
user: 'postgres',
database: 'myapp',
// ... other config
});
const prisma = new PrismaClient();
async function getUserByEmail(email) {
try {
const result = await prisma.user.findUnique({
where: { email },
select: { id: true, email: true }
});
return result;
} catch (err) {
console.error('Error fetching user:', err);
}
}
Welcome to our chapter on Transactions & Performance Tips! In this section, we'll explore how to optimize your Node.js applications when working with databases. You'll learn about connection pooling, indexing, and best practices for using transactions effectively.
Database connections are critical resources in any Node.js application. Understanding how to manage them efficiently can have a significant impact on your application's performance.
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'testdb',
connectionLimit: 10
});
Proper indexing is essential for optimizing database queries. Without indexes, your database engine may perform full table scans, leading to poor performance.
CREATE INDEX idx_user_name ON users(name);
Transactions allow you to execute multiple database operations as a single unit of work. They provide atomicity, consistency, isolation, and durability (ACID properties).
pool.getConnection((err, connection) => {
if (err) throw err;
const query = 'START TRANSACTION;';
connection.query(query, function(err, results) {
// Execute transaction operations here
const commitQuery = 'COMMIT;';
connection.query(commitQuery, function(err, results) {
connection.release();
if (err) throw err;
});
});
});
In this chapter, we've covered essential topics for optimizing your Node.js database applications. By properly managing connections, implementing effective indexing strategies, and using transactions wisely, you can significantly improve your application's performance and reliability.
Question 1 of 14