Database Connectivity


Database Connectivity Interview with follow-up questions

1. Can you explain how to connect a Node.js application with a MySQL database?

Use a MySQL driver — but pick mysql2, not the old mysql package. mysql2 is the actively-maintained choice with a promise API, prepared statements, and connection pooling. In production you want a pool, not a single connection:

import mysql from 'mysql2/promise';

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,     // creds from env, never hard-coded
  database: process.env.DB_NAME,
  connectionLimit: 10,
});

// parameterized query — prevents SQL injection
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);

The points interviewers reward:

  • Use a connection pool (reuse connections) rather than createConnection per request — that's a common performance bug.
  • Parameterize every query (? placeholders / execute) to prevent SQL injection; never string-concatenate user input.
  • Keep credentials in environment variables, use TLS for remote DBs, and handle errors / retries.
  • Many teams add a query builder or ORM (Knex, Prisma, Drizzle, Sequelize) on top for migrations and typed queries. The legacy mysql package still works but is unmaintained — prefer mysql2 (or an ORM).
↑ Back to top

Follow-up 1

What npm package would you use for this?

For connecting a Node.js application with a MySQL database, you can use the mysql package. This package provides a straightforward way to interact with MySQL databases in Node.js.

Follow-up 2

How would you handle database errors?

To handle database errors when connecting a Node.js application with a MySQL database, you can use the error handling mechanism provided by the mysql package. Here's an example:

connection.connect(function(err) {
  if (err) {
    console.error('Error connecting to MySQL database: ' + err.stack);
    return;
  }

  console.log('Connected to MySQL database as id ' + connection.threadId);
});

In this example, the connection.connect() method takes a callback function that is called when the connection is established. If there is an error, the error object will be passed to the callback function. You can then handle the error accordingly.

Follow-up 3

How can you pool connections to a MySQL database in Node.js?

To pool connections to a MySQL database in Node.js, you can use the mysql package's connection pooling feature. Connection pooling allows you to reuse and manage a pool of database connections, which can improve the performance and scalability of your application. Here's an example:

const mysql = require('mysql');

const pool = mysql.createPool({
  connectionLimit: 10,
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'mydatabase'
});

pool.getConnection(function(err, connection) {
  if (err) {
    console.error('Error getting MySQL connection from pool: ' + err.stack);
    return;
  }

  // Use the connection for database operations

  connection.release(); // Release the connection back to the pool
});

In this example, mysql.createPool() is used to create a connection pool with a specified connection limit. The pool.getConnection() method is then used to get a connection from the pool. Once you're done with the connection, you should release it back to the pool using the connection.release() method.

Follow-up 4

Can you write a sample code to connect Node.js with MySQL?

Sure! Here's a sample code to connect a Node.js application with a MySQL database using the mysql package:

const mysql = require('mysql');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'mydatabase'
});

connection.connect(function(err) {
  if (err) {
    console.error('Error connecting to MySQL database: ' + err.stack);
    return;
  }

  console.log('Connected to MySQL database as id ' + connection.threadId);

  // Perform database operations

  connection.end(); // Close the connection
});

In this example, we create a connection object using mysql.createConnection() and pass the necessary connection details. The connection.connect() method is used to establish the connection, and the callback function is called when the connection is established. Inside the callback function, you can perform various database operations. Finally, the connection.end() method is used to close the connection.

2. How would you connect a Node.js application to a MongoDB database?

Use the official mongodb driver, or Mongoose (ODM) if you want schemas/models. With the native driver:

import { MongoClient } from 'mongodb';

const client = new MongoClient(process.env.MONGO_URI);  // URI from env
await client.connect();
const db = client.db('app');
const users = db.collection('users');

const user = await users.findOne({ _id: id });
await users.insertOne({ name: 'Ada' });

With Mongoose:

import mongoose from 'mongoose';
await mongoose.connect(process.env.MONGO_URI);
const User = mongoose.model('User', new mongoose.Schema({ name: String }));
await User.create({ name: 'Ada' });

Best practices to mention: the modern driver/Mongoose are promise-based (use async/await); connect once at startup and reuse the client — the driver manages an internal connection pool, so don't connect per request; keep the URI/credentials in env vars and use TLS (e.g. MongoDB Atlas mongodb+srv://); validate input even though it's NoSQL (guard against NoSQL injection by not passing raw user objects into queries); and handle connection errors plus graceful shutdown (client.close()).

↑ Back to top

Follow-up 1

What is the role of Mongoose in connecting Node.js with MongoDB?

Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides a higher-level abstraction for interacting with MongoDB, making it easier to define schemas, perform validations, and create queries. Mongoose also provides features like middleware, population, and hooks, which can enhance the functionality of your Node.js application when working with MongoDB.

Follow-up 2

What is the difference between MongoDB and MySQL in terms of Node.js connectivity?

MongoDB is a NoSQL database, while MySQL is a relational database. In terms of Node.js connectivity, the main difference lies in the way data is stored and queried.

  • MongoDB stores data in a flexible, JSON-like format called BSON (Binary JSON), which allows for dynamic schemas and nested data structures. It uses a document-based model, where data is organized into collections of documents.

  • MySQL stores data in tables with predefined schemas, using a structured query language (SQL) for querying and manipulating data. It follows a relational model, where data is organized into tables with rows and columns.

When connecting Node.js with MongoDB, you would typically use the MongoDB driver or an ODM library like Mongoose. For MySQL, you would use the mysql package or an ORM library like Sequelize.

Follow-up 3

How would you handle MongoDB connection errors in Node.js?

To handle MongoDB connection errors in Node.js, you can use the error event provided by the MongoDB driver or the error callback function in Mongoose.

In the MongoDB driver:

code

In Mongoose:

code

Follow-up 4

Can you write a sample code to connect Node.js with MongoDB?

Sure! Here's a sample code to connect Node.js with MongoDB using the MongoDB driver:

code

And here's an example using Mongoose:

code

3. What are the best practices for database connectivity in Node.js?

The best practices interviewers expect:

  1. Use connection pooling — reuse a pool of connections rather than opening one per request; this is the single biggest performance factor. Initialize the pool/client once at startup and share it.
  2. Parameterize queries — always use placeholders / prepared statements to prevent SQL (and NoSQL) injection; never concatenate user input.
  3. Keep credentials in environment variables / a secrets manager — never hard-code them; use TLS for connections.
  4. Handle errors and timeoutstry/catch around awaited queries, sensible connection/query timeouts, and retry transient failures.
  5. Manage transactions correctly — wrap multi-step writes in a transaction and release connections in a finally block so the pool doesn't leak.
  6. Tune and observe — add proper indexes, avoid N+1 queries, paginate large result sets, and monitor slow queries.
  7. Cache hot reads (Redis) to offload the DB, and gracefully close connections on shutdown.

A current note: tools like Prisma, Drizzle, Knex, or Sequelize/TypeORM bundle pooling, parameterization, migrations, and typed queries, which helps enforce these practices. And for serverless, prefer a connection pooler (e.g. PgBouncer / driver-level pooling) to avoid exhausting DB connections.

↑ Back to top

Follow-up 1

How would you manage database connections in a large scale Node.js application?

In a large scale Node.js application, managing database connections efficiently is crucial for performance and scalability. Some strategies for managing database connections in such applications include:

  1. Connection pooling: Use a connection pooling library like generic-pool or pg-pool to manage a pool of reusable database connections. This allows you to limit the number of connections and reuse them across multiple requests.

  2. Connection management middleware: Implement a middleware that manages the lifecycle of database connections. This middleware can acquire a connection from the pool at the beginning of a request and release it back to the pool after the request is processed.

  3. Automatic reconnection: Handle connection failures gracefully by implementing automatic reconnection mechanisms. This ensures that the application can recover from temporary database outages without manual intervention.

  4. Load balancing: If your application is deployed in a distributed environment, consider using a load balancer to distribute the database connection requests across multiple database servers.

  5. Connection monitoring: Implement connection monitoring to detect and handle idle or stale connections. This can help prevent connection leaks and improve overall performance.

Follow-up 2

What are the security considerations when connecting to a database?

When connecting to a database in Node.js, it is important to consider the following security aspects:

  1. Secure credentials: Store database credentials securely, preferably in environment variables or a configuration file that is not accessible to unauthorized users.

  2. Use parameterized queries or prepared statements: This helps prevent SQL injection attacks by separating SQL code from user input.

  3. Implement proper authentication and authorization: Ensure that only authorized users can access the database and that they have appropriate permissions. Use strong passwords and consider implementing two-factor authentication.

  4. Encrypt sensitive data: If you are storing sensitive data in the database, consider encrypting it to protect it from unauthorized access.

  5. Limit database privileges: Grant the minimum required privileges to database users. Avoid using privileged accounts for regular application operations.

  6. Regularly update and patch the database software: Keep the database software up to date with the latest security patches to protect against known vulnerabilities.

  7. Monitor database activity: Implement logging and monitoring mechanisms to detect and respond to suspicious database activity, such as unauthorized access attempts or unusual query patterns.

Follow-up 3

How can you ensure efficient data retrieval in Node.js?

To ensure efficient data retrieval in Node.js, you can follow these practices:

  1. Optimize database queries: Write efficient and optimized database queries by using appropriate indexes, avoiding unnecessary joins, and minimizing the amount of data retrieved.

  2. Use pagination or limit the result set: When retrieving large amounts of data, consider using pagination or limiting the result set to reduce the amount of data transferred over the network.

  3. Implement caching: Use caching mechanisms like Redis or Memcached to cache frequently accessed data. This can significantly reduce the load on the database and improve performance.

  4. Use asynchronous operations: Node.js is designed to handle asynchronous operations efficiently. Use asynchronous database drivers and avoid blocking operations to ensure that the application remains responsive.

  5. Use streaming: If you are dealing with large datasets, consider using streaming techniques to process the data in chunks instead of loading it all into memory at once.

  6. Denormalize data: In some cases, denormalizing the data by duplicating it across multiple tables or collections can improve query performance by reducing the need for complex joins.

  7. Monitor and optimize performance: Regularly monitor database performance and optimize queries and indexes based on usage patterns and performance metrics.

4. How would you handle database transactions in Node.js?

A transaction groups multiple statements into one atomic unit — all succeed (commit) or all are undone (rollback) — preserving ACID guarantees (e.g. transferring money between accounts). In Node you do this through your driver/ORM, with async/await and try/catch.

Raw SQL with a pooled connection (you must take a single connection so all statements run in the same transaction):

const conn = await pool.getConnection();
try {
  await conn.beginTransaction();
  await conn.execute('UPDATE accounts SET bal = bal - ? WHERE id = ?', [100, 1]);
  await conn.execute('UPDATE accounts SET bal = bal + ? WHERE id = ?', [100, 2]);
  await conn.commit();
} catch (err) {
  await conn.rollback();
  throw err;
} finally {
  conn.release();           // always return the connection to the pool
}

With an ORM it's cleaner — e.g. Sequelize's sequelize.transaction(async (t) => { ... }) or Prisma's prisma.$transaction([...]) auto-commit/rollback.

The points that earn marks: use one dedicated connection for the whole transaction (not the pool directly), always rollback on error and release in finally to avoid leaking connections, keep transactions short to reduce lock contention, and be aware of isolation levels. For MongoDB, multi-document transactions exist too (session.withTransaction) but require a replica set.

↑ Back to top

Follow-up 1

Can you explain the concept of ACID in database transactions?

ACID is an acronym that stands for Atomicity, Consistency, Isolation, and Durability. It is a set of properties that guarantee reliable processing of database transactions. Here's a brief explanation of each concept:

  • Atomicity: Atomicity ensures that a transaction is treated as a single, indivisible unit of work. Either all the operations within a transaction are successfully completed, or none of them are. If any operation fails, the entire transaction is rolled back.

  • Consistency: Consistency ensures that a transaction brings the database from one valid state to another. It enforces integrity constraints and business rules, preventing the database from being left in an inconsistent state.

  • Isolation: Isolation ensures that concurrent transactions do not interfere with each other. Each transaction is executed as if it were the only transaction running, even though multiple transactions may be executing concurrently.

  • Durability: Durability ensures that once a transaction is committed, its changes are permanent and will survive any subsequent failures, such as power outages or system crashes.

Follow-up 2

How would you handle a transaction failure in Node.js?

In Node.js, you can handle a transaction failure by using try-catch blocks and rollback the transaction in the catch block. Here's an example:

(async () => {
  const transaction = await sequelize.transaction();

  try {
    // Perform database operations within the transaction
    await User.create({ name: 'John Doe', email: '[email protected]' }, { transaction });
    await User.update({ name: 'Jane Doe' }, { where: { id: 1 }, transaction });

    // Commit the transaction
    await transaction.commit();
    console.log('Transaction committed successfully.');
  } catch (error) {
    // Rollback the transaction on failure
    await transaction.rollback();
    console.error('Transaction failed:', error);
  }
})();

Follow-up 3

What are the differences in handling transactions in SQL and NoSQL databases in Node.js?

The handling of transactions in SQL and NoSQL databases differs in Node.js. Here are some key differences:

  • SQL Databases: SQL databases, such as MySQL or PostgreSQL, have built-in support for ACID transactions. They provide features like transaction isolation levels, explicit transaction control statements (e.g., BEGIN, COMMIT, ROLLBACK), and support for rollback and commit operations. Libraries like sequelize provide an ORM layer to handle transactions in SQL databases.

  • NoSQL Databases: NoSQL databases, such as MongoDB or Cassandra, typically do not have built-in support for ACID transactions. They often follow a different data model and trade transactional consistency for scalability and performance. However, some NoSQL databases offer limited transactional capabilities, such as atomic updates or multi-document transactions. Libraries like mongoose provide transactional support for NoSQL databases in Node.js.

5. Can you explain how to use promises to handle asynchronous database operations in Node.js?

Promises represent the eventual result of an async operation, letting you write database code without nested callbacks. In practice you rarely build promises by hand — modern drivers already return them, so you just await:

// mysql2/promise — driver returns promises directly
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);

// chaining form
pool.execute('SELECT * FROM users WHERE id = ?', [id])
  .then(([rows]) => console.log(rows))
  .catch((err) => console.error(err));

async/await is the cleanest way to consume them, with try/catch for errors:

async function getUser(id) {
  try {
    const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);
    return rows[0];
  } catch (err) {
    logger.error(err);
    throw err;                 // let the caller decide
  }
}

Points that complete the answer: you only need new Promise(...) to wrap an old callback-based API (or use util.promisify) — wrapping a driver that's already promise-based is an anti-pattern. Use Promise.all for independent queries to run them concurrently, but keep transactional statements on one connection in order. Always handle rejections (await in try/catch, or .catch()), since an unhandled rejection crashes modern Node.

↑ Back to top

Follow-up 1

What are the advantages of using promises over callbacks?

There are several advantages of using promises over callbacks:

  1. Readability: Promises provide a more readable and maintainable code structure compared to nested callbacks. Promises allow you to chain multiple asynchronous operations together, making the code easier to understand.

  2. Error handling: Promises have built-in error handling through the catch method. This allows you to handle errors in a centralized way, instead of having to handle them in each callback.

  3. Composition: Promises can be easily composed together using methods like then and catch. This makes it easier to handle complex asynchronous flows and avoid callback hell.

  4. Synchronization: Promises provide a way to synchronize multiple asynchronous operations. You can use methods like Promise.all or Promise.race to wait for multiple promises to resolve or reject.

Overall, promises provide a more elegant and efficient way to handle asynchronous operations in Node.js.

Follow-up 2

How would you handle errors when using promises?

When using promises, you can handle errors using the catch method. The catch method is called when a promise is rejected, either by calling the reject function inside the promise or by an error occurring during the asynchronous operation.

Here's an example of handling errors when using promises:

getUserById(1)
  .then((user) => {
    console.log(user);
  })
  .catch((error) => {
    console.error(error);
  });

Follow-up 3

Can you provide a code example of using promises for database operations in Node.js?

Sure! Here's an example of using promises for database operations in Node.js using the Sequelize library:

const Sequelize = require('sequelize');

const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql'
});

const User = sequelize.define('user', {
  name: Sequelize.STRING,
  email: Sequelize.STRING
});

sequelize.sync()
  .then(() => {
    return User.create({
      name: 'John Doe',
      email: '[email protected]'
    });
  })
  .then((user) => {
    console.log(user);
  })
  .catch((error) => {
    console.error(error);
  });

Live mock interview

Mock interview: Database Connectivity

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.