DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • A Beginner's Guide to Back-End Development
  • Creating a Secure REST API in Node.js
  • 6 Tips to Help You Write Cleaner Code in Node.js
  • 10 Node.js Security Practices

Trending

  • Solid Testing Strategies for Salesforce Releases
  • Is Agile Right for Every Project? When To Use It and When To Avoid It
  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  • Comprehensive Guide to Property-Based Testing in Go: Principles and Implementation
  1. DZone
  2. Data Engineering
  3. Databases
  4. Utilizing Database Hooks Like a Pro in Node.js

Utilizing Database Hooks Like a Pro in Node.js

This article will explain how to use database hooks in your Node.js applications to solve specific problems that might arise in your development journey.

By 
Brett Hoyer user avatar
Brett Hoyer
·
Mar. 27, 23 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
5.7K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, I’ll explain how to use database hooks in your Node.js applications to solve specific problems that might arise in your development journey.

Many applications require little more than establishing a connection pool between a server, database, and executing queries. However, depending on your application and database deployments, additional configurations might be necessary.

For example, multi-region distributed SQL databases can be deployed with different topologies depending on the application use case. Some topologies require setting properties on the database on a per-session basis.

Let’s explore some of the hooks made available by some of the most popular database clients and ORMs in the Node.js ecosystem.

Laying the Foundation

The Node.js community has many drivers to choose from when working with the most popular relational databases. Here, I’m going to focus on PostgreSQL-compatible database clients, which can be used to connect to YugabyteDB or another PostgreSQL database.

Sequelize, Prisma, Knex and node-postgres are popular clients with varying feature sets depending on your needs. I encourage you to read through their documentation to determine which best suits your needs.

These clients come with hooks for different use cases. For instance:

  • Connection hooks: Execute a function immediately before or after connecting and disconnecting from your database.
  • Logging hooks: Log messages to stdout at various log levels.
  • Lifecycle hooks: Execute a function immediately before or after making calls to the database.

In this article, I’ll cover some of the hooks made available by these clients and how you can benefit from using them in your distributed SQL applications.

I’ll also demonstrate how to use hooks to hash a user's password before creation and how to set runtime configuration parameters after connecting to a multi-region database with read replicas.

Sequelize 

The Sequelize ORM has a number of hooks for managing the entire lifecycle of your database transactions. 

The beforeCreate lifecycle hook can be used to hash a password before creating a new user:

JavaScript
 
User.beforeCreate(async (user, options) => {
 const hashedPassword = await hashPassword(user.password);
 user.password = hashedPassword;
});


Next, I’m using the afterConnect connection hook to set session parameters. 

With this YugabyteDB deployment, you can execute reads from followers to reduce latencies, and eliminate the need to read from the primary cluster nodes:

JavaScript
 
const config = {
  host: process.env.DB_HOST,
  port: 5433,
  dialect: "postgres",
  dialectOptions: {
    ssl: {
      require: true,
      rejectUnauthorized: true,
      ca: [CERTIFICATE],
    },
  },
  pool: {
    max: 5,
    min: 1,
    acquire: 30000,
    idle: 10000,
  },
  hooks: {
     async afterConnect(connection) {
       if (process.env.DB_DEPLOYMENT_TYPE === "multi_region_with_read_replicas") {
         await connection.query("set yb_read_from_followers = true; set session characteristics as transaction read only;");
       }
     },
   },
};

const connection = new Sequelize(
   process.env.DATABASE_NAME,
   process.env.DATABASE_USER,
   process.env.DATABASE_PASSWORD,
   config
);


By using this hook, each database session in the connection pool will set these parameters upon establishing a new connection:

  • set yb_read_from_followers = true;: This parameter controls whether or not reading from followers is enabled.
  • set session characteristics as transaction read only;: This parameter applies the read-only setting to all statements and transaction blocks that follow.

Prisma

Despite being the ORM of choice for many in the Node.js community, at the time of writing, Prisma doesn’t contain many of the built-in hooks found in Sequelize. Currently, the library contains hooks to handle the query lifecycle, logging, and disconnecting, but offers no help before or after establishing connections.

Here’s how you can use Prisma’s lifecycle middleware to hash a password before creating a user:

JavaScript
 
prisma.$use(async (params, next) => {
 if (params.model == 'User' && params.action == 'create') {
   params.args.data.password = await hashPassword(params.args.data.password);
 }

 return next(params)
})

const create = await prisma.user.create({
 data: {
   username: 'bhoyer',
   password: 'abc123'
 },
})


To set session parameters to make use of our read replicas, we’ll have to execute a statement before querying our database:

JavaScript
 
await prisma.$executeRaw(`set yb_read_from_followers = true; set session characteristics as transaction read only;`);
const users = await prisma.user.findMany();


If you need to immediately establish a connection in your connection pool to set a parameter, you can connect explicitly with Prisma to forgo the lazy connection typical of connection pooling.

Prisma has the log levels of query , error, info, and warn. Queries can be handled as events using event-based logging:

JavaScript
 
const prisma = new PrismaClient({
 log: [
   {
     emit: 'event',
     level: 'query',
   },
   {
     emit: 'stdout',
     level: 'error',
   },
   {
     emit: 'stdout',
     level: 'info',
   },
   {
     emit: 'stdout',
     level: 'warn',
   },
 ],
});

prisma.$on('query', (e) => {
 console.log('Query: ' + e.query);
 console.log('Params: ' + e.params);
 console.log('Duration: ' + e.duration + 'ms');
});


This can be helpful in development when working on query tuning in a distributed system. 

Here’s how you can make use of the beforeExit hook to access the database before exiting:

JavaScript
 
const prisma = new PrismaClient();

prisma.$on('beforeExit', async () => {
 // PrismaClient still available
 await prisma.issue.create({
   data: {
     message: 'Connection exiting.' 
   },
 })
});


Knex

Knex is a lightweight query builder, but it does not have the query middleware found in more full-featured ORMs. 

To hash a password, you can process this manually using a custom function:

JavaScript
 
async function handlePassword(password) {
	const hashedPassword = await hashPassword(password);
	return hashedPassword;
}
const password = await handlePassword(params.password);
knex('users').insert({...params, password});


The syntax required to achieve a connection hook in the Knex.js query builder is similar to that of Sequelize. Here’s how we can set our session parameters to read from YugabyteDB’s replica nodes:

JavaScript
 
const knex = require('knex')({
  client: 'pg',
  connection: {/*...*/},
  pool: {
    afterCreate: function (connection, done) {
      connection.query('set yb_read_from_followers = true; set session characteristics as transaction read only;', function (err) {
        if (err) {
          //Query failed
          done(err, conn);
        } else {
          console.log("Reading from replicas.");
	     done();
        }
      });
    }
  }
});


node-postgres

The node-postgres library is the most low-level of all of the libraries discussed. Under the hood, the Node.js EventEmitter is used to emit connection events.

A connect event is triggered when a new connection is established in the connection pool. Let’s use it to set our session parameters. I’ve also added an error hook to catch and log all error messages:

JavaScript
 
const config = {
  user: process.env.DB_USER,
  host: process.env.DB_HOST,
  password: process.env.DB_PASSWORD,
  port: 5433,
  database: process.env.DB_NAME,
  min: 1,
  max: 10,
  idleTimeoutMillis: 5000,
  connectionTimeoutMillis: 5000,
  ssl: {
    rejectUnauthorized: true,
    ca: [CERTIFICATE],
    servername: process.env.DB_HOST,
  }
};
const pool = new Pool(config);

pool.on("connect", (c) => {
  c.query("set yb_read_from_followers = true; set session characteristics as transaction read only;");
});

pool.on("error", (e) => {
  console.log("Connection error: ", e);
});


There aren’t any lifecycle hooks at our disposal with node-postgres, so hashing our password will have to be done manually, like with Prisma:

JavaScript
 
async function handlePassword(password) {
	const hashedPassword = await hashPassword(password);
	return hashedPassword;
}
const password = await handlePassword(params.password);
const user = await pool.query('INSERT INTO user(username, password) VALUES ($1, $2) RETURNING *', [params.username, password]);


Wrapping Up

As you can see, hooks can solve a lot of the problems previously addressed by complicated and error-prone application code. Each application has a different set of requirements and brings new challenges. You might go years before you need to utilize a particular hook in your development process, but now, you’ll be ready when that day comes.

Look out for more from me on Node.js and distributed application development. Until then, keep on coding!

Connection pool Database Hook JavaScript Node.js

Opinions expressed by DZone contributors are their own.

Related

  • A Beginner's Guide to Back-End Development
  • Creating a Secure REST API in Node.js
  • 6 Tips to Help You Write Cleaner Code in Node.js
  • 10 Node.js Security Practices

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!