The Complete Guide to Becoming a Web Developer: Part 7
Ahmed Radwan
Posted on August 13, 2023
In the world of web development, there are a few tools that have revolutionized the way we build web applications, and Node.js, NPM, and Express are at the forefront of this revolution. These tools have made it possible for developers to use JavaScript, a language traditionally used for front-end development, for server-side programming as well, leading to the rise of full-stack JavaScript development.
- Introduction
- First Look at Node.js
- Installing Node.js and NPM
- Exploring Modules and the NPM World
- Creating Servers with Express.js
- Creating Dynamic HTML with Templating
- Defining RESTful Routes
- Middleware in Express.js
- Error Handling in Express.js
- Database Integration with Express.js
- Best practices for working with Node.js, NPM, and Express
- Common Mistakes and How to Avoid Them
- Conclusion
Introduction
Node.js is a runtime environment that allows us to execute JavaScript on the server side. It's built on Chrome's V8 JavaScript engine and brings event-driven programming to web servers, enabling the development of fast web servers in JavaScript.
NPM, which stands for Node Package Manager, is a tool that comes with Node.js when you install it. It's a package manager for the JavaScript programming language and is the default package manager for Node.js. It allows developers to install, share, and package software and manage dependencies in their projects.
Express, on the other hand, is a fast, unopinionated, and minimalist web framework for Node.js. It provides a simple way to write servers and web applications and has become a standard server framework for Node.js. It is built to manage routes, requests, and views along with handling APIs and building robust web applications and APIs.
These tools are essential for any web developer because they provide a complete package for developing server-side applications. Node.js allows you to use JavaScript on the server, NPM provides a way to manage packages and dependencies, and Express makes it easy to set up your server and add routes, middleware, and more.
In this guide, we will dive deep into these tools, starting from your first contact with Node.js, exploring modules and the NPM world, creating servers with Express.js, creating dynamic HTML with templating, and defining RESTful routes. By the end of this guide, you will have a solid understanding of these tools and how to use them in your web development journey. So, let's get started!
First Look at Node.js
Node.js is an asynchronous event-driven JavaScript runtime designed to build scalable network applications. It's highly efficient for handling many connections concurrently. In a typical "Hello World" example, upon each connection, a callback is fired, but if there is no work to be done, Node.js will sleep. This is in contrast to the more common concurrency model where OS threads are employed, which can be relatively inefficient and difficult to use.
Here's a simple example of a Node.js program:
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
In this example, we're creating a simple HTTP server that responds with "Hello World" for every request. The server is listening on port 3000. When you run this program, you should see the message "Server running at http://127.0.0.1:3000/" in your terminal.
Node.js is similar in design to systems like Ruby's Event Machine and Python's Twisted, but it takes the event model a bit further by presenting an event loop as a runtime construct instead of as a library. This means that Node.js enters the event loop after executing the input script and exits when there are no more callbacks to perform, much like browser JavaScript.
HTTP is a first-class citizen in Node.js, designed with streaming and low latency in mind, making Node.js well-suited for the foundation of a web library or framework. Despite being designed without threads, Node.js can still take advantage of multiple cores in your environment through child processes and the cluster module, which allows you to share sockets between processes to enable load balancing over your cores.
Now that we have a basic understanding of what Node.js is, let's move on to installing Node.js and NPM.
Installing Node.js and NPM
Installing Node.js and NPM (Node Package Manager) is a straightforward process. Node.js is the runtime that executes JavaScript code outside of a browser, and NPM is the default package manager for Node.js. It's used to install Node.js programs from the npm registry, organize versions and dependencies of these programs, and make it easier to share and distribute code.
You can download Node.js and NPM from the official Node.js website (https://nodejs.org/). There are different versions available, but for most users, the LTS (Long Term Support) version is the best choice. The LTS version is stable and receives long-term support. After downloading the installer, run it and follow the prompts in the setup wizard.
After installation, you can verify that Node.js and NPM are installed correctly by opening a terminal or command prompt and running the following commands:
node -v
npm -v
The node -v
command should print the Node.js version, and npm -v
should print the NPM version. If you see version numbers, that means Node.js and NPM are installed correctly.
Now that we have Node.js and NPM installed, let's write our first Node.js program. We'll create a simple program that prints "Hello, World!" to the console.
Create a new file named app.js
and open it in your text editor. Add the following code to the file:
console.log("Hello, World!");
Save the file and go back to your terminal. Navigate to the directory where you saved app.js
and run the following command:
node app.js
You should see "Hello, World!" printed on the console. Congratulations, you've just run your first Node.js program!
In the next section, we'll dive deeper into the world of Node.js and explore modules and the NPM ecosystem.
Exploring Modules and the NPM World
In the world of Node.js, a package contains all the files you need for a module. Modules are JavaScript libraries that you can include in your project. NPM, which stands for Node Package Manager, is a tool that allows you to easily download and use these packages. When you install Node.js, NPM is automatically installed on your computer, ready to use.
Understanding Modules in Node.js
In Node.js, modules are a set of functions that you want to include in your application. You can think of them as JavaScript libraries because they are reusable pieces of code that perform a specific task. Node.js has a built-in module system that allows you to include modules in your code with the require()
function.
Here's an example of how you can include the built-in HTTP module in your Node.js application:
var http = require('http');
In this example, the require()
function returns an object because the HTTP module returns its functionality as an object. You can use this object to create a server, make a request to a server, and perform other HTTP-related tasks.
NPM
NPM is a package manager for Node.js. It hosts thousands of free packages that you can download and use in your projects. When you install Node.js, NPM is also installed on your computer, so you're ready to start using it right away.
Installing and Using NPM Packages
Downloading and installing a package with NPM is easy. You just need to open your command line interface and tell NPM to download the package you want. For example, if you want to download a package called "upper-case", you would type the following command:
npm install upper-case
After running this command, NPM creates a folder named "node_modules" where it places the "upper-case" package. All packages you install in the future will also be placed in this folder.
To use the "upper-case" package in your Node.js application, you would include it with the require()
function, just like you would with a built-in module:
var uc = require('upper-case');
Now you can use the upperCase()
function from the "upper-case" package to convert a string to upper case:
console.log(uc.upperCase("Hello World!")); // Outputs: "HELLO WORLD!"
In this section, we've just scratched the surface of what you can do with modules and NPM in Node.js.
Creating Servers with Express.js
Creating servers with Express.js is a straightforward process that allows developers to quickly get a server up and running. Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It simplifies the process of writing server code, and it's perfect for building APIs and handling HTTP requests.
What is Express.js?
Express.js is a web application framework for Node.js. It's designed for building web applications and APIs. It's minimal, meaning it doesn't come with everything out of the box. Instead, it provides a simple interface for building server-side JavaScript applications, and you can extend it with various plugins for added functionality.
Setting Up an Express Server
To set up an Express server, you first need to install Express.js. You can do this by running npm install express
in your terminal. Once Express.js is installed, you can create a new file (let's call it app.js
) and write the following code:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});
This code creates a new Express application and sets up a single route (/
) that responds with 'Hello World!'. The app.listen()
function starts the server on the specified port.
Handling Requests and Responses
In the example above, we used the app.get()
function to set up a route for HTTP GET requests. The first argument is the path, and the second argument is a callback function that takes a request and a response object. The request object (req
) contains information about the HTTP request, such as the URL, HTTP headers, and any data sent by the client. The response object (res
) is used to send data back to the client.
In our callback function, we simply call res.send('Hello World!')
to send a response back to the client. This will send the string 'Hello World!' as the HTTP response body.
Express.js provides a number of other functions for handling different types of HTTP requests (like app.post()
for POST requests), and for sending different types of responses (like res.json()
for sending JSON data).
Remember, the beauty of Express.js lies in its simplicity and flexibility. You can build a wide range of applications using just these basic concepts, and you can extend your application with middleware and plugins as needed.
Creating Dynamic HTML with Templating
Creating dynamic HTML is a fundamental part of web development. It allows us to create web pages that can change and adapt based on data, user interactions, or other factors. One of the ways we can create dynamic HTML in Node.js applications is by using templating engines.
Introduction to Templating Engines
A templating engine is a tool that allows developers to write HTML markup with dynamic values. These dynamic values are placeholders that get replaced with actual data when the page is rendered. This allows us to create a single template for a web page and then use it to generate many different pages, each with its own data.
There are many templating engines available for Node.js, including EJS, Pug (formerly Jade), Handlebars, and many others. They all have their own syntax and features, but they all serve the same basic purpose: to make it easier to generate dynamic HTML.
Using EJS for Dynamic HTML Generation
EJS, or Embedded JavaScript, is a simple templating engine that lets you generate HTML markup with plain JavaScript. It's easy to use and has a syntax that's similar to regular HTML, which makes it a good choice for beginners.
To use EJS, you first need to install it using NPM:
npm install ejs
Then, in your Express.js application, you can tell Express to use EJS as its templating engine like this:
app.set('view engine', 'ejs');
Now you can create EJS templates in your views directory. Here's an example of what an EJS template might look like:
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<h1>Welcome, <%= user.name %>!</h1>
<p>You have <%= messages.length %> new messages.</p>
</body>
</html>
In this template, <%= user.name %>
and <%= messages.length %>
are placeholders that will be replaced with actual data when the template is rendered.
To render an EJS template, you can use the res.render()
function in Express:
app.get('/', (req, res) => {
res.render('index', { user: { name: 'John' }, messages: [] });
});
Templating Best Practices
When working with templates, it's important to keep a few best practices in mind:
- Keep your templates simple. Templates are meant to generate HTML, not to contain complex logic. If you find yourself writing a lot of JavaScript in your templates, consider moving some of that logic into your controllers or models.
- Don't mix data fetching with rendering. Your templates shouldn't be responsible for fetching data from a database or an API. Fetch the data first, then pass it to the template for rendering.
- Escape user input to prevent XSS attacks. Most templating engines, including EJS, automatically escape user input to prevent cross-site scripting (XSS) attacks. However, it's still a good idea to be aware of this risk and to always escape user input if you're inserting it into your templates.
- Use partials for reusable components. Most templating engines allow you to create "partials", which are small templates that you can include in other templates. This is great for things like headers, footers, or any other components that you want to reuse across multiple pages.
Defining RESTful Routes
Defining routes is a key part of building any web application. In this section, we'll explore what RESTful architecture is, how to define routes in Express.js, and how to work with route parameters and query strings.
Understanding RESTful Architecture
REST, or Representational State Transfer, is an architectural style for designing networked applications. A RESTful web application exposes information about itself in the form of resources (data entities) that clients can interact with.
In a RESTful application, each resource is identified by a specific URL, and the server responds to different HTTP methods like GET, POST, PUT, DELETE, etc., which represent different operations that can be performed on the resource.
Here's a simple example of what RESTful routes might look like for a blog application:
- GET /posts: Retrieve a list of all posts
- POST /posts: Create a new post
- GET /posts/๐ Retrieve a specific post
- PUT /posts/๐ Update a specific post
- DELETE /posts/๐ Delete a specific post
Defining Routes in Express.js
In Express.js, you can define routes using methods of the Express app object that correspond to HTTP methods. Here's how you might define the routes for the blog application mentioned above:
const express = require('express');
const app = express();
app.get('/posts', (req, res) => {
// Retrieve and return all posts
});
app.post('/posts', (req, res) => {
// Create a new post
});
app.get('/posts/:id', (req, res) => {
// Retrieve and return a specific post
});
app.put('/posts/:id', (req, res) => {
// Update a specific post
});
app.delete('/posts/:id', (req, res) => {
// Delete a specific post
});
app.listen(3000, () => console.log('Server is running on port 3000'));
Route Parameters and Query Strings
In the example above, :id
is a route parameter. Route parameters are named URL segments that you can use to capture the values specified at their position in the URL. You can access these values using req.params
.
For example, in the route GET /posts/:id
, if you navigate to /posts/42
, req.params.id
will be '42'.
Query strings, on the other hand, are a way to send data to the server as part of the URL, but not as part of the route. They are included after a question mark in the URL, and are composed of key-value pairs separated by ampersands.
For example, in the URL /posts?category=tech&author=John
, the query string is category=tech&author=John
, and you can access these values in Express using req.query
. So req.query.category
will be 'tech', and req.query.author
will be 'John'.
Middleware in Express.js
Middleware is a fundamental concept in Express.js that you'll use in almost every project. In this section, we'll explore what middleware is, how to use the built-in middleware provided by Express, and how to create your own custom middleware.
Understanding Middleware
Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the applicationโs request-response cycle. The next function is a function in the Express router which, when invoked, executes the middleware succeeding the current middleware.
Middleware functions can perform the following tasks:
- Execute any code.
- Make changes to the request and the response objects.
- End the request-response cycle.
- Call the next middleware in the stack.
If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.
Using Built-In Express Middleware
Express.js comes with several built-in middleware functions that you can use right out of the box. Here are a few examples:
-
express.json()
: This is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser. -
express.static()
: This middleware function is used to serve static files. You pass the name of the directory toexpress.static()
to start serving the files directly. For example, if you store images in a directory named "images", you can useexpress.static('images')
to start serving the images.
Here's how you might use these middleware functions in an Express app:
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.static('images'));
app.listen(3000, () => console.log('Server is running on port 3000'));
Creating Custom Middleware
In addition to using the built-in middleware, you can also create your own custom middleware. Here's an example of a simple custom middleware function that logs the current date and time for each request:
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.listen(3000, () => console.log('Server is running on port 3000'));
In this example, app.use()
is used to load the middleware function. The middleware function logs the current date and time, and then calls next()
to pass control to the next middleware function in the stack.
But what if we didn't call next()?
If you don't call next()
at the end of your middleware function, the request-response cycle will halt at that middleware. This means that any middleware functions that are supposed to run after the current one will not be executed. This can lead to issues where the server doesn't send a response back to the client, causing the client to hang or timeout.
Here's an example to illustrate this:
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
// next(); // If we don't call next(), the request stops here
});
app.get('/', (req, res) => {
res.send('Hello, world!');
});
app.listen(3000, () => console.log('Server is running on port 3000'));
In this example, if you make a GET request to the root route ('/'), you won't receive the 'Hello, world!' response. This is because the middleware function that logs the time doesn't call next()
, so the request never reaches the route handler for the root route.
However, there are cases where you might intentionally want to end the request-response cycle in a middleware function without calling next()
. For example, you might have a middleware function that checks if a user is authenticated. If the user is not authenticated, you could send a response with an error message and not call next()
, effectively preventing the request from proceeding to the route handler.
Error Handling in Express.js
Here's a detailed overview of error handling in Express.js:
Handling Synchronous Errors
Errors that occur in synchronous code inside route handlers and middleware require no extra work. If synchronous code throws an error, then Express will catch and process it. For example:
app.get('/', (req, res) => {
throw new Error('BROKEN') // Express will catch this on its own.
})
Handling Asynchronous Errors
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next()
function, where Express will catch and process them. For example:
app.get('/', (req, res, next) => {
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
next(err) // Pass errors to Express.
} else {
res.send(data)
}
})
})
Starting with Express 5, route handlers and middleware that return a Promise will call next(value)
automatically when they reject or throw an error.
The Default Error Handler
Express comes with a built-in error handler that takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.
If you pass an error to next()
and you do not handle it in a custom error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace. The stack trace is not included in the production environment.
Writing Error Handlers
Define error-handling middleware functions in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next)
. For example:
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).send('Something broke!')
})
You define error-handling middleware last, after other app.use()
and routes calls.
For more details, you can refer to the Express.js Error Handling Guide.
Database Integration with Express.js
Introduction to MongoDB
MongoDB is a popular NoSQL database that uses a document-oriented data model. It's a database of choice for many Node.js applications, including Express.js apps. MongoDB stores data in a format that's similar to JSON, which makes it intuitive to use from JavaScript. It's also highly scalable and can handle large amounts of data distributed over multiple servers.
Using Mongoose for MongoDB Interaction
To interact with MongoDB in a Node.js application, we often use an Object Data Modeling (ODM) library, and Mongoose is one of the most popular choices. Mongoose acts as a front end to MongoDB and provides a simple and intuitive API for interacting with the database. It allows you to define schemas for your data, validate data before it's saved, and define instance and static methods for your data models.
To use Mongoose, you first need to install it in your project using npm:
npm install mongoose
Then, you can require it in your code and use it to define schemas and models:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const SomeModelSchema = new Schema({
a_string: String,
a_date: Date,
});
const SomeModel = mongoose.model('SomeModel', SomeModelSchema);
In this example, SomeModelSchema
is a schema that defines the structure of the documents in the MongoDB collection. SomeModel
is a model that provides methods for querying the database, creating new documents, and more.
CRUD Operations with Mongoose
Mongoose provides methods for performing Create, Read, Update, and Delete (CRUD) operations on the database.
Create: You can create a new document and save it to the database using the save
method of a Mongoose model instance:
const someModelInstance = new SomeModel({ a_string: 'Hello', a_date: Date.now() });
someModelInstance.save((err) => {
if (err) return handleError(err);
// saved!
});
Read: You can retrieve documents from the database using the find
method of a Mongoose model:
SomeModel.find({ a_string: 'Hello' }, (err, docs) => {
if (err) return handleError(err);
// docs is an array of all documents that match the query
});
Update: You can update documents in the database using the updateOne
, updateMany
, or findOneAndUpdate
methods of a Mongoose model:
SomeModel.updateOne({ a_string: 'Hello' }, { a_string: 'Hi' }, (err, res) => {
if (err) return handleError(err);
// res.nModified is the number of documents updated
});
Delete: You can delete documents from the database using the deleteOne
, deleteMany
, or findOneAndDelete
methods of a Mongoose model:
SomeModel.deleteOne({ a_string: 'Hi' }, (err) => {
if (err) return handleError(err);
// deleted
});
These are just the basics of using Mongoose with MongoDB in an Express.js application. Mongoose provides many more features, such as data validation, middleware, and population, which can help you build robust and efficient web applications. We will learn more about it in future articles.
Best practices for working with Node.js, NPM, and Express
Code Organization and Structure: Organize your code into modules and use the module.exports and require() syntax to import and export functions, objects, or values from one module to another. This will make your code more readable and maintainable.
// greet.js
module.exports = function greet(name) {
return `Hello, ${name}!`;
};
// app.js
const greet = require('./greet');
console.log(greet('World')); // Outputs: Hello, World!
Security Best Practices: Always validate and sanitize user input to protect your application from security threats like SQL Injection and Cross-Site Scripting (XSS). Use packages like helmet to secure your Express apps by setting various HTTP headers and express-validator for server-side data validation.
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
Performance Optimization: Use the Node.js built-in 'cluster' module to spawn a process for each core on your server's CPU. This allows your application to handle more traffic and operate faster. Also, consider using a reverse proxy like Nginx to serve static files and cache content, which can significantly improve your application's performance.
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
const express = require('express');
const app = express();
// ...
}
Error Handling: Always handle errors properly in your Node.js applications. Use middleware for centralized error handling in your Express.js applications. This will help you to avoid unhandled promise rejections and uncaught exceptions that can crash your Node.js process.
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
Keep your packages up to date: Regularly update the packages you've installed via NPM. This will ensure you have the latest features and security patches. You can update all packages to their latest version by running npm update
in your terminal.
Common Mistakes and How to Avoid Them
Callback Hell and How to Avoid It: Callback hell, also known as Pyramid of Doom, is a situation where callbacks are nested within callbacks, making the code hard to read and understand. This is a common issue in Node.js because it uses a lot of asynchronous operations.
fs.readdir(source, function (err, files) {
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
// ...
})
}
})
To avoid callback hell, you can use Promises or async/await, which are built into modern versions of JavaScript. Here's how you can rewrite the above code using async/await:
async function readDirectory(source) {
try {
const files = await fs.promises.readdir(source);
files.forEach((filename, fileIndex) => {
console.log(filename);
// ...
});
} catch (err) {
console.log('Error finding files: ' + err);
}
}
Error Handling Mistakes: One of the most common mistakes is not handling errors properly or not handling them at all. This can lead to unexpected application crashes. Always use a .catch()
with Promises or a try/catch
block with async/await to handle errors.
async function readDirectory(source) {
try {
const files = await fs.promises.readdir(source);
// ...
} catch (err) {
console.error('Error reading directory:', err);
}
}
Memory Leaks and How to Prevent Them: Memory leaks can cause your Node.js application to use increasing amounts of memory over time, which can degrade performance and eventually cause the application to crash. Common causes of memory leaks include global variables, forgotten timers or callbacks, and not closing database connections.
To avoid memory leaks, make sure to clear callbacks, close database connections, and be careful with global variables. Also, use tools like Node.js process.memoryUsage() and heapdump to monitor and debug memory usage.
let globalVar = new Array(5e8); // This will consume a lot of memory!
globalVar = null; // Make sure to nullify the variable when you're done with it.
Conclusion
And there you have it! We've taken a deep dive into the world of Node.js, NPM, and Express, the backbone of modern web development. We've covered everything from your first contact with Node.js, exploring modules and the NPM world, creating servers with Express.js, creating dynamic HTML with templating, defining RESTful routes, understanding middleware, handling errors, and integrating with the database. We've also shared some tips and best practices for working with these tools and discussed common mistakes and how to avoid them.
As we wrap up, here are a few final pieces of advice:
- Keep Practicing: The more you use Node.js, NPM, and Express, the more comfortable you'll become. Try building different types of projects to gain a wide range of experiences.
- Stay Up-to-Date: These tools are constantly evolving, so it's important to stay up-to-date with the latest features and best practices. Follow the official documentation and blogs, join relevant communities, and don't be afraid to ask questions.
- Learn by Doing: Reading and understanding concepts are important, but real learning happens when you start building. Don't be afraid to get your hands dirty.
- Don't Be Afraid of Errors: Errors are your friends. They guide you toward the right path. Learn to read and understand them, and you'll become a better developer.
Additional Resources
I'd like to leave you with some additional resources that you can use to further your learning. These resources are highly recommended for anyone looking to deepen their understanding of these tools and modern web development in general.
- Node.js Official Documentation: This is the official documentation for Node.js. It's comprehensive, well-organized, and a great place to start if you're looking for detailed information on a specific topic.
- Express.js Official Documentation: Similar to the Node.js documentation, the official Express.js documentation is a fantastic resource for understanding the ins and outs of Express.js.
- NPM Official Documentation: This is the official documentation for NPM. It's a great place to learn more about how NPM works and how to use it effectively.
- Mozilla Developer Network (MDN): MDN is a treasure trove of web development knowledge. It's not specific to Node.js, NPM, or Express, but it's a great resource for understanding web development concepts in general.
- Stack Overflow: Stack Overflow is a community of developers helping each other solve coding problems. It's a great place to ask questions and learn from others' experiences.
- Node School: Node School offers interactive tutorials for Node.js, NPM, and many other related topics. It's a great way to learn by doing.
- Express.js GitHub Repository: Looking at the source code of a library or framework can be a great way to understand how it works under the hood. The Express.js GitHub repository is open source and available for anyone to explore.
Posted on August 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.