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
createConnectionper 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
mysqlpackage still works but is unmaintained — prefermysql2(or an ORM).
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()).
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:
- 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.
- Parameterize queries — always use placeholders / prepared statements to prevent SQL (and NoSQL) injection; never concatenate user input.
- Keep credentials in environment variables / a secrets manager — never hard-code them; use TLS for connections.
- Handle errors and timeouts —
try/catcharound awaited queries, sensible connection/query timeouts, and retry transient failures. - Manage transactions correctly — wrap multi-step writes in a transaction and release connections in a
finallyblock so the pool doesn't leak. - Tune and observe — add proper indexes, avoid N+1 queries, paginate large result sets, and monitor slow queries.
- 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.
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:
Connection pooling: Use a connection pooling library like
generic-poolorpg-poolto manage a pool of reusable database connections. This allows you to limit the number of connections and reuse them across multiple requests.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.
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.
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.
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:
Secure credentials: Store database credentials securely, preferably in environment variables or a configuration file that is not accessible to unauthorized users.
Use parameterized queries or prepared statements: This helps prevent SQL injection attacks by separating SQL code from user input.
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.
Encrypt sensitive data: If you are storing sensitive data in the database, consider encrypting it to protect it from unauthorized access.
Limit database privileges: Grant the minimum required privileges to database users. Avoid using privileged accounts for regular application operations.
Regularly update and patch the database software: Keep the database software up to date with the latest security patches to protect against known vulnerabilities.
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:
Optimize database queries: Write efficient and optimized database queries by using appropriate indexes, avoiding unnecessary joins, and minimizing the amount of data retrieved.
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.
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.
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.
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.
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.
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.
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
sequelizeprovide 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
mongooseprovide 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.
Follow-up 1
What are the advantages of using promises over callbacks?
There are several advantages of using promises over callbacks:
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.
Error handling: Promises have built-in error handling through the
catchmethod. This allows you to handle errors in a centralized way, instead of having to handle them in each callback.Composition: Promises can be easily composed together using methods like
thenandcatch. This makes it easier to handle complex asynchronous flows and avoid callback hell.Synchronization: Promises provide a way to synchronize multiple asynchronous operations. You can use methods like
Promise.allorPromise.raceto 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
- Read your scene and goals
- Talk it out; goals tick off live
- Get a score and stronger lines
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.