View on GitHub

Notes

reference notes

CRUD Operations

CRUD is an essential concept in web development, particularly when working with databases. It represents four fundamental operations that can be performed on data: Create, Read, Update, and Delete. These operations are the building blocks of many database-driven applications and are crucial for managing data effectively. Let’s dive into each of these operations:

  1. Create: The “Create” operation involves adding new records or entries to a database. For example, in a library management system, users can create new records for books, authors, or genres. This operation typically corresponds to the HTTP method POST in web applications.

  2. Read: The “Read” operation is about retrieving data from the database. Users can access and view existing records. In our library example, this operation allows users to search for books, list authors, or browse genres. It corresponds to the HTTP method GET.

  3. Update: The “Update” operation allows users to modify or edit existing records in the database. For instance, users can update book details like the title, author, or publication date. This operation is often associated with the HTTP method PUT.

  4. Delete: The “Delete” operation enables users to remove records or entries from the database. If a book is no longer relevant, users can delete it from the library system. This operation corresponds to the HTTP method DELETE.

It’s essential to note that the availability and permissions for these CRUD operations can vary depending on the application’s requirements and user roles. For instance, some users may have permissions to create and update records, while others may only have read access.

In the context of web development with Express.js or other web frameworks, CRUD operations often align with specific HTTP methods, making it easier to design RESTful APIs for your application.

MVC (Model-View-Controller)

MVC is a well-established architectural pattern commonly used in web development to structure code and separate concerns. It stands for Model, View, and Controller, and it helps organize your application into distinct components, making it more maintainable and scalable. Let’s break down the MVC pattern:

  1. Model: The “Model” represents the core data structure of your application. Each type of data that your application deals with, such as books in a library system, authors, or genres, has its corresponding model. Models define the structure, attributes, and behavior of these data entities. They handle interactions with the database, including data retrieval, storage, and validation.

  2. View: The “View” is responsible for presenting data to users in a user-friendly format. It generates the user interface (UI) and displays information retrieved from the models. In web applications, views are often created using templating engines, as discussed earlier. Views are not responsible for business logic or data manipulation; they focus solely on rendering data.

  3. Controller: The “Controller” acts as an intermediary between the model and the view. It receives user input and decides which view to display and what data to populate that view with. Controllers contain the application’s logic, handling user requests and coordinating interactions between models and views. They determine which CRUD operations to perform based on user actions.

Here’s an example to illustrate the MVC pattern:

Suppose you’re building a photo-uploading website. In this case:

MVC helps maintain a clear separation of concerns, making your codebase more modular and easier to maintain. It also facilitates collaboration among developers, as they can work on different components of the application independently. As you progress in your web development journey, you’ll find that applying the MVC pattern becomes essential for building robust and scalable applications.

Choosing and Interacting with Databases in Express.js

When building Express.js applications, one of the critical decisions you’ll need to make is choosing a database to store and manage your data. Express apps can work with a wide range of databases supported by Node.js, and the choice depends on factors like your project’s requirements, your familiarity with the database, performance considerations, scalability, and more. Here are some popular databases you can use with Express:

  1. PostgreSQL: PostgreSQL is a powerful, open-source relational database management system known for its robustness, extensibility, and SQL support. It’s an excellent choice for applications requiring complex queries, data integrity, and scalability.

  2. MySQL: MySQL is another widely used open-source relational database. It’s known for its speed, reliability, and ease of use. MySQL is suitable for various types of applications, from small projects to large-scale websites.

  3. Redis: Redis is an in-memory data store often used for caching and real-time data processing. It’s exceptionally fast and efficient, making it an excellent choice for applications that require high-speed data retrieval.

  4. SQLite: SQLite is a lightweight, serverless, self-contained database engine that’s perfect for smaller applications or mobile development. It’s easy to set up and doesn’t require a separate server.

  5. MongoDB: MongoDB is a popular NoSQL database that uses a document-oriented data model. It’s designed to store, retrieve, and manage data in JSON-like documents, making it well-suited for applications with flexible and unstructured data.

Once you’ve selected a database, you’ll need to decide on the best way to interact with it. There are two primary approaches:

  1. Native Query Language: You can use the native query language of your chosen database (e.g., SQL for PostgreSQL or MySQL) to perform database operations. This approach provides fine-grained control over queries and is often more performant for complex operations.

  2. Object Data Model (ODM) or Object-Relational Model (ORM): An ODM or ORM maps your application’s data to database tables and provides a higher-level abstraction for interacting with the database. Instead of writing raw queries, you work with JavaScript objects. This approach simplifies database interactions and is beneficial for rapid development.

When deciding between these approaches, consider factors like your familiarity with the query language, performance requirements, and the need for cross-database compatibility.

Choosing an ORM/ODM

If you opt for the ODM or ORM approach, there are several solutions available in the Node.js ecosystem. The choice of ORM/ODM depends on your specific project needs and preferences. Here are a few popular options:

  1. Mongoose: Mongoose is a widely used ODM for MongoDB. It provides a straightforward way to define schemas, perform CRUD operations, and work with MongoDB in a Node.js environment. Mongoose is especially useful for projects that involve unstructured data or need to work with MongoDB-specific features.

  2. Waterline: Waterline is a flexible ORM extracted from the Sails.js framework. It offers support for multiple databases, making it suitable for applications that need to work with various database systems.

  3. Sequelize: Sequelize is a powerful promise-based ORM for relational databases like PostgreSQL, MySQL, and SQLite. It provides robust support for transactions, migrations, and complex SQL queries.

  4. Objection: Objection.js is an SQL-friendly ORM that works with relational databases such as SQLite, PostgreSQL, and MySQL. It aims to make it easier to use SQL and take full advantage of the underlying database engine.

  5. Bookshelf: Bookshelf.js is an ORM that focuses on simplicity and extensibility. It’s particularly useful for projects requiring one-to-one, one-to-many, and many-to-many relations in relational databases.

When selecting an ORM/ODM, consider factors like database compatibility, ease of use, community support, and the features that best align with your project’s requirements. Each of these libraries has its strengths and may be more suitable for specific use cases.

Using Mongoose and MongoDB

If you choose to work with MongoDB, Mongoose is an excellent choice as an ODM. MongoDB is a NoSQL database that stores data in JSON-like documents, and Mongoose simplifies working with these documents in Node.js applications. Mongoose offers features like schema definition, validation, and query building, making it a popular choice for Node.js developers working with MongoDB.

In summary, your choice of database and interaction method will depend on your project’s specific needs. Consider factors like data structure, performance, ease of use, and community support when making these decisions.

Asynchronous Database APIs and Promises

When working with databases in modern web applications, it’s crucial to understand that Database APIs operate asynchronously. This means that methods for creating, finding, updating, or deleting records don’t block the execution of your code. Instead, these methods return immediately, and the code to handle the success or failure of the operation runs at a later time when the database operation completes. This non-blocking behavior is essential for ensuring that your server remains responsive to other requests while waiting for potentially time-consuming database interactions.

Handling Asynchronous Operations in JavaScript

JavaScript provides several mechanisms for dealing with asynchronous behavior. Historically, JavaScript relied heavily on passing callback functions to asynchronous methods to handle success and error cases. However, in modern JavaScript, callbacks have largely been replaced by Promises.

Promises

Promises are objects that are immediately returned by asynchronous methods, representing their future state. When the operation initiated by a Promise completes, the Promise object is “settled.” It then either resolves with an object representing the operation’s result or rejects with an error.

Using await with Promises

One of the most readable and understandable ways to work with Promises is by using the await keyword within an async function. Here’s how this approach works:

  1. Mark a function as asynchronous using the async keyword.
  2. Inside the asynchronous function, apply await to any method that returns a Promise.
  3. When the asynchronous function is executed, it pauses at the first await until the Promise settles.
  4. From the perspective of the surrounding code, the asynchronous function returns, allowing other code to run.
  5. Later, when the Promise settles, the await method inside the asynchronous function returns with the result, or it throws an error if the Promise was rejected.
  6. The code in the asynchronous function then continues to execute until it either encounters another await, pausing again, or until all the code in the function has run.

Let’s look at an example to illustrate this:

async function myFunction {
  // ...
  await someObject.methodThatReturnsPromise();
  // ...
  await aFunctionThatReturnsPromise();
  // ...
}

try {
  // ...
  myFunction();
  // ...
} catch (e) {
 // error handling code
}

In this example, myFunction() is an asynchronous function called within a try...catch block. When myFunction() is executed, code execution pauses at methodThatReturnsPromise() until the Promise resolves. It then continues to aFunctionThatReturnsPromise() and waits again. If an error is thrown in the asynchronous function (which can happen if the Promise returned by either of the methods is rejected), the code in the catch block handles the error.

Running Promises in Parallel with Promise.all()

If you have asynchronous methods that don’t depend on each other, you can run them in parallel to complete the entire operation more quickly. JavaScript provides the Promise.all() method for this purpose. Promise.all() takes an iterable of Promises as input and returns a single Promise. This returned Promise fulfills when all of the input Promises fulfill, providing an array of their fulfillment values. If any of the input Promises reject, the returned Promise also rejects with the first rejection reason encountered.

Here’s how you can use Promise.all():

async function myFunction {
  // ...
  const [resultFunction1, resultFunction2] = await Promise.all([
     functionThatReturnsPromise1(),
     functionThatReturnsPromise2()
  ]);
  // ...
  await anotherFunctionThatReturnsPromise(resultFunction1);
}

In this example, Promise.all() is used to await the completion of multiple asynchronous functions in parallel. Once both functionThatReturnsPromise1() and functionThatReturnsPromise2() complete, await returns, and the resultFunction1 and resultFunction2 variables are populated with their respective results. The function then continues to the next await, waiting until the Promise returned by anotherFunctionThatReturnsPromise() settles.