Sessions and Redis for Beginners

Sessions and Redis for Beginners

Implementing Sessions with Redis: A Step-by-Step Beginners Guide

A session is a server-side storage mechanism that retains information across a user's interaction with a web application. Unlike cookies, which store data on the client-side, session data is securely stored on the server and identified using a unique session ID.

Think of sessions like a skilled waiter who remembers your order without having to ask twice. Just as you rely on them to recall your preferences during your entire visit, sessions reliably track your data as you navigate through the site.

Some of the common uses of Sessions are

  • Authentication: Sessions are often used to keep a user logged in across multiple pages

  • User Preferences: Store user preferences like themes or language settings.

  • Shopping Cart: Sessions maintain the state of a user's shopping cart in an e-commerce application

  • Form Data: Temporarily store form data as the user navigates through a multi-step form.

However, for simple use cases like basic form data, client-side solutions like cookies and localStorage are often sufficient. They offer simplicity, efficiency, and reduce server load. Sessions might be considered overkill unless there are specific security or consistency requirements that justify the additional complexity and server resources.

Above is a simple flowchart showing how sessions work at a high level. We'll go through each step in detail to help you understand the process better.

  1. User initiates Session

When a user interacts with a web application (e.g., logs in or starts a new browsing session), the server creates a unique session identifier (SessionID). This SessionID is a randomly generated string that uniquely identifies the user's session.

The purpose of the SessionID is to allow the server to recognise the user's requests and maintain state across multiple interactions.

Typically, the server-side application generates the SessionID using a secure random number generator or a hashing function to ensure uniqueness and randomness, reducing the risk of SessionID collision or guessing by malicious users.

  1. SessionID sent back to client

Once the server generates the SessionID, it sends this identifier back to the client as part of the HTTP response. This SessionID is usually sent as a cookie.

  1. Client makes subsequent requests

For every subsequent request that the client makes to the server, the browser automatically includes the SessionID stored in the cookie.

Upon receiving the request, the server looks up the SessionID in the server's session store (which could be an in-memory store like Redis, or the server's memory). If the SessionID matches an existing session in the store, the server retrieves the session data associated with that SessionID (e.g., user information, preferences, authentication status).

The server will then process the request using the session data and sends back the appropriate response to the client.

How to use sessions

Managing user sessions is a crucial aspect of building a robust backend application. In this tutorial, we'll walk you through the process of setting up sessions in your Node.js backend.

Steps[0] - Project setup for Beginners

  • First, create a new, empty folder on your computer. This folder will contain all the files and code for your project. You can name the folder anything you prefer, but for the purposes of this guide, we'll use the name my-project.

  • Once you have created the folder, open your command line interface. This could be Terminal on macOS/Linux or Command Prompt/PowerShell on Windows

  • In your CLI, navigate to the folder you just created,

  • Now that you're inside your project folder in the command line interface, run the following command:

npm init -y

When you run npm init -y, it creates a package.json file in your project folder. This file is crucial for managing your project because it defines informations about your project, lists the dependencies your project needs to run and specifies scripts that can be run with npm to do things like running your project or tests.

Steps[1] - Installing dependencies

To enable session management in your Node.js application, you'll need to install the express and express-session packages.

Please make sure you are in the correct directory, which is your my project folder, you can install these packages by running the command below in your terminal.

yarn add express express-session

What are we installing?

  • express:

    • A web framework for Node.js that simplifies the process of creating server-side applications.
  • express-session:

    • Middleware for managing sessions in Express applications. It helps you handle user sessions, enabling you to store user-specific data and manage user states across different requests.

With these packages installed, you'll be ready to start using sessions in your application. In the next steps, we'll set up and configure sessions in your Express application.

Steps[2] - Setting up the server

Now that we have installed the necessary packages, the next step is to set up the server for our Node.js application using Express. This involves creating a basic server that can handle incoming requests and send responses.

Create the Server File:

  • In your project directory, create a new file called server.js. This file will contain the code to set up and run your server.

Write the Server Code:

  • Open server.js in your code editor and add the following code to set up a basic server:
const express = require('express');
const session = require('express-session');

const app = express();
const port = 3000;

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true
}));

app.get('/login', (req, res) => {
  // Authentication logic here
  req.session.userId = 'exampleUserId'; // Store user ID in session
  res.send('Logged in');
});

app.get('/dashboard', (req, res) => {
  if (req.session.userId) {
    res.send(`Welcome, user ${req.session.userId}`);
  } else {
    res.status(401).send('Unauthorized');
  }
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

Explanation of the Code

  • Importing Required Modules:
const express = require('express');
const session = require('express-session');

We import the express module to create the server and handle routes and the express-session module to manage sessions.

  • Initialize Express:
const app = express();
const port = 3000;

Next we create an instance of an Express application and set the port number to 3000

  • Setting Up Session Middleware:
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true
}));

Next we configure the session middleware with a secret key. The resave and saveUninitialized options control the behaviour of session saving.

Breakdown of Options:

In configuring express-session, the secret option is crucial as it sets a unique, strong, and private string used to sign the session ID cookie, ensuring its integrity and security by preventing tampering. The resave option, when set to false, enhances performance by avoiding unnecessary session saves when the data hasn’t changed. The saveUninitialized option, set to true, forces new, unmodified sessions to be saved to the session store,, although it may result in storing a large number of unused sessions.

  • Defining Routes
app.get('/login', (req, res) => {
  // Authentication logic here
  req.session.userId = 'exampleUserId'; // Store user ID in session
  res.send('Logged in');
});

app.get('/dashboard', (req, res) => {
  if (req.session.userId) {
    res.send(`Welcome, user ${req.session.userId}`);
  } else {
    res.status(401).send('Unauthorized');
  }
});

Next we define two routes /login and /dashboard. The login route stores a hardcoded userId in session and returns "Logged in" as the server response

While the dashboard route checks if the userId of the user trying to access the dashboard route is in the session store, if true this would mean the user had logged in with the /login route and is authorized because the user information is still in the session store. On the other hand if the userId is not found in the session store the route will return and a 401 http status code and an unauthorized error.

This is basically how sessions work, they store data on the server and maintain that data across multiple requests and routes.

Just a heads up—these route implementations are intentionally minimal because we are not focusing on that here. Normally, you'd use a POST request for the login route since it's safer and handles user data better. Plus, you'd have more validations before storing anything in the session. For now, we're using a GET route and a hardcoded userId just to keep things simple and easy to test.

  • Starting the Server
app.listen(port, () => {
 console.log(`Server running on http://localhost:${port}`);
});

Next, we start the server and begin listening for incoming requests on the specified port (3000), which we defined earlier. Once the server is up and running, it logs a confirmation message to the console.

Steps[3] - Running and Testing the Server

  • Start the Server:

In your terminal, navigate to your my-project directory and run the following command to start the server.

node server.js

You should see a message in the terminal indicating that the server is running ⬇️

Server running on http://localhost:3000
  • Test the Server

We will test three cases to ensure our sessions implementation works as expected.

Case 1: Accessing the Dashboard Without Logging In

  1. Navigate to http://localhost:3000/dashboard on your web browser(please make sure the server is running).

  2. You should see an "unauthorized" message. This indicates that our user ID is not in the sessions store yet, which is the expected behaviour.

Case 2: Logging In and Accessing the Dashboard

  1. Navigate to http://localhost:3000/login on your web browser.

  2. You should get a "Logged in" response on the webpage. If you see this message, it means the login process was successful.

  3. Navigate to http://localhost:3000/dashboard again.

  4. When the page loads, you should see a "Welcome, user exampleUserId" response. This confirms that our sessions implementation on the server is functioning correctly.

Case 3: Restarting the server and Accessing the Dashboard

  1. Open your terminal, specifically where your Server is running and press ctrl + c on your keyboard, this command should stop the server.

  2. Now run node server.js on your terminal again to restart the server

  3. Navigate to http://localhost:3000/dashboard , after the page loads did you notice anything? It appears we are unathorized again, hmm why? 🤔 if our data gets wiped everything our server crashes or restarts it could get really messy if our app grows, this is where Redis comes in 🔽

Redis 📦

While traditional session management works well for simple applications, it can present challenges as your application scales. Standard sessions (like the code we wrote above) are typically stored in-memory on a single server(your machine in this instance), which means they can be lost if the server crashes or is restarted. Additionally, managing consistent session data across multiple servers can be complex.

This is where Redis comes into play. Redis is an in-memory data store that can function as a distributed session store, ensuring that session data remains available and consistent across all servers. By leveraging Redis, you can achieve high availability and improved performance, making it an ideal solution for session management in scalable applications.

Setting up redis

  • Go to Redis cloud - https://redis.io/try-free/, If you don't have an account, sign up for one.

  • After signing up, you will be asked to create a free database, Choose a cloud vendor that suits your needs. The options usually include Amazon Web Services (AWS), Google Cloud, and Microsoft Azure. From the "Region" menu, select a region that is geographically closest to your location to minimize latency.

  • Once your selections are made, click on Get Started to create your Redis instance.

And that's it! Your free database has been created. After clicking Get Started, you'll be redirected to your database's dashboard. On this page, you'll find the information required to connect to it.

By clicking on Connect, as shown in the image above, a sidebar will appear, showing different methods for connecting to your Redis database instance.

For now, all we need to do is click on Redis Client. This will display information on how to connect to your database, including a dropdown menu where you can select the client you want to connect from (for this tutorial, we will select Node.js). A code snippet will also be provided, showing how to connect from the selected client.

Copy your password, host and port, we will be needing them in the next section.

Implementing Sessions with Redis On our backend

Before we start making changes to our backend code we will need to install a few dependencies that will be needed to implement Redis on our backend.

Run the following command to install the packages required

npm install connect-redis redis

Now that we have the necessary information (password, host, and port) and dependencies to connect to our Redis database instance, we need to make a few changes to our server.js file. Below is the revised version of the code with Redis implemented. We will walk through the changes step by step, as we did before. ⬇️

const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default
const redis = require('redis');

const redisClient = redis.createClient({
  socket: {
    host: "**your redis db host**", // Redis server host
    port: "**your redis db port**", // Redis server port
  },
  password: "**your redis db password**",,
});

redisClient.connect().catch(console.error);

const app = express();
const port = 3000;

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } // Set to true if using HTTPS
}));

app.get('/login', (req, res) => {
  req.session.userId = 'exampleUserId'; // Store user ID in session
  res.send('Logged in');
});

app.get('/dashboard', (req, res) => {
  if (req.session.userId) {
    res.send(`Welcome, user ${req.session.userId}`);
  } else {
    res.status(401).send('Unauthorized');
  }
});

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

If you compare the updated code with the previous version, you'll notice some changes. Here’s a walkthrough of those modifications.

const RedisStore = require('connect-redis').default;
const redis = require('redis');

const RedisStore = require('connect-redis').default; imports and initializes the connect-redis package.

const redis = require('redis'); imports the redis package for use in our application, the redis package helps us connect to our Redis database.

const redisClient = redis.createClient({
  socket: {
    host: **your redis db host**,  // Redis server host
    port: **your redis db port**,         // Redis server port
  },
  password: **your redis db password**,
});

redisClient.connect().catch(console.error);

This part of the code creates a new Redis client instance. The client is configured to connect to our Redis server using the options provided in the object passed as an argument. Here, you can input the host, port, and password that we copied after creating our Redis database.

The line redisClient.connect().catch(console.error); is used to establish a connection to the Redis server.

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: false,
  cookie: { secure: false } // Set to true if using HTTPS
}));

If you look at this part of the code, you'll notice a couple of new lines added to the session options object.

store: new RedisStore({ client: redisClient }): This line configures the session to use our initialized Redis store. If this field were empty, as it was in our previous code, express-session would default to using its built-in memory store.

cookie: { secure: false }: This option in express-session controls whether session cookies are sent over HTTP. Setting secure: false means cookies are transmitted over both HTTP and HTTPS, which is common in development environments where HTTPS might not be used. However, for production environments, it’s recommended to set secure: true and serve your app over HTTPS to protect session cookies from interception.

And that's it! 🎉, You’ve successfully implemented Redis on your server. You can now revisit the three test cases from earlier in this article and observe that you remain authenticated even after restarting your server.

Summary

Sessions enable servers to maintain stateful interactions with clients across multiple requests, despite HTTP being a stateless protocol. This capability allows servers to track user-specific data, offer personalized experiences, and manage user authentication effectively. Redis enhances this functionality by providing a robust, high-performance data store for session management. By using Redis, servers can store session data in a centralized, persistent, and scalable way, ensuring that user sessions remain intact even after server restarts or crashes. This leads to a more reliable and resilient application, especially as it grows.

I hope this article has helped you understand how to leverage sessions and Redis for effective session management. These basics are just the starting point. Redis can be used to build complex and powerful applications, such as real-time chat systems, gaming leaderboards, recommendation engines, and caching layers to speed up data retrieval. If you’re interested in a step-by-step tutorial on any of these applications or have other questions, please let me know in the comments below.

Don’t forget to like the article if you found it helpful, and check out the code on GitHub via the link below. Happy coding! 🫡

Link to github repo - https://github.com/gcadigwe/redis-tutorial