Building a basic server with node.js and express

Building a basic server with node.js and express

Have you ever been curious about how things function in the world of Backend Development? How are servers built, and what technologies are involved in creating servers, APIs, databases, and more? How does it all fit together? Let’s explore one of the simplest and most common methods of building a backend server using Node.js and Express.

A brief overview of Node.js

Node.js is a cross-platform JavaScript runtime environment compatible with nearly all popular operating systems. It allows developers to build command-line tools and write server-side scripts using JavaScript. Running on the JavaScript V8 engine, Node.js enables JavaScript code to be executed outside of a web browser. You can find more information about Node.js below.

https://nodejs.org/en/about

About Express

Express is a backend web application framework that allows developers to create servers, RESTful APIs, and more. In this session, we’ll focus on setting up an Express project, running a server, and sending requests to it to better understand how Express functions. Express is commonly used in large projects and provides a project generator for quick setup. However, since this might confuse beginners, we will not use the generator. Instead, we’ll start with a blank project and gradually build it up step-by-step.

What we will be building

We will begin by setting up a blank project using Node.js and Express. We’ll create a server and run the server to see it in action. Let’s start by covering the prerequisites:

Prerequisites

The only requirement is to install Node.js, which can be downloaded from its official website linked below. It’s recommended to download the LTS (Long Term Support) version, as the latest version may have unexpected issues.

https://nodejs.org/en

Once you have downloaded and installed Node.js, you can verify the installation by opening the terminal and running the following command.

node --version

If a version number appears, it indicates that Node.js has been successfully installed.

Creating the project

Go to the folder where you like to store your projects and create a new folder named foodie-be, or you can run the following command in your terminal.

mkdir foodie-be

Navigate to the foodie-be project

cd foodie-be

Initiating npm

To use Express, we need to set up npm, which will create a package.json file. This file lets us include dependencies like Express. Run the following command to initialize npm, and add the --yes flag to automatically accept all default options. You can modify the settings later in the package.json file.

npm init --yes

The following package.json will be generated

package.json
{
  "name": "foodie-be",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

Let’s add a .gitignore file, as we may need to push our project to GitHub. To prevent sensitive data from being uploaded, we use .gitignore to specify which files or folders should be excluded from version control.

.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

Installing Express

It is time to install Express and use it to create our server. We can add Express as a dependency and get started with building our server. Run your terminal at the root of your project and enter the following command:

npm install express
pacakge.json
{
  "name": "foodie-be",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.19.2"
  }
}

Two files are generated: package-lock.json and node_modules. Both of these files are crucial and understanding their roles is important.

  • package-lock.json – This file is created or updated whenever changes are made to package.json or node_modules.
  • node_modules – This directory contains the external dependencies needed for the project. When you install a dependency, as we did earlier, it is stored locally in the node_modules folder.

Creating the Entry Point

To set up a server, we need to specify the entry point file for Node.js, which is the starting file for the project. This file isn’t created yet, so let’s create it now. We’ll name the entry file server.js.

touch server.js

To let Node.js recognize server.js as the entry point, we need to update the main key in the package.json file to point to server.js instead of index.js.

pacakge.json
{
  "name": "foodie-be",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.19.2"
  }
}

Creating & Running the Server

Before we start writing the logic to create and run a server, it’s important to understand what a server is, how it functions, and why it is necessary.

Understanding Server Workflow

The Restaurant Example

Consider the example of a restaurant: A customer places an order, which is taken by a waiter and sent to the kitchen. The kitchen then prepares the order, and finally, it is served to the customer, as illustrated in the diagram below.

This is precisely the role of a server. A client (like a customer) can be any entity, such as a web application, mobile application, or any interface on a hardware device that sends a request (similar to placing an order with a waiter). This request is then forwarded to the backend (the kitchen), where it is processed (the order is prepared) and returned to the client (the order is served) in the form of a response.

Routing over the internet

The internet is filled with URLs (Uniform Resource Locators), which consist of a scheme, subdomain, second-level domain (if needed), top-level domain, and subdirectories. Essentially, a URL acts as a path to a directory containing files on the internet. Domains correspond to numerical IP addresses, but we use readable and easy-to-remember domain names to simplify website access for users.

When developing a server, we use a domain called “localhost,” which corresponds to the IP address 127.0.0.1, along with a port number where the server is running. For example, if our server is set up on port 3000, the full address would be 127.0.0.1:3000. We will be testing our server by entering this address in the browser’s URL bar after starting the server.

Traditional ways to send requests to a server

A backend server can retrieve data through various methods. In the past, SOAP (Simple Object Access Protocol) was commonly used for this purpose. However, in the modern era, REST APIs and GraphQL APIs have become two of the most popular methods for retrieving data from the backend.

Responses from the backend

A request sent to the server can receive various types of responses, such as an object, a webpage, a redirect, and more. To keep things simple for now, we’ll have the server return a webpage with the text ‘Hello, Server!’.

HTTP Proverbs

HTTP (HyperText Transfer Protocol) utilizes verbs to transmit data across the network in various ways. Some of the most commonly used verbs are GET, POST, PUT, and DELETE. While there are others, these are the core methods.

Implementing the server

To implement the server, we will be utilizing the Express library that we installed in the following way:

server.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hello Server!');
})

app.listen(3000, () => console.log('Server running on port 3000'));
  • Line 1: We import the Express library.
  • Line 2: We initialize an Express application using the express() function.
  • Line 4: With the Express library, we create a GET endpoint by defining a route and a callback function that takes request (req) and response (res) as parameters. The route ‘/’ represents the base URL, or the server’s homepage.
  • Line 5: We use the response (res) object to send back a simple text message, ‘Hello Server!’.
  • Line 8: We use the listen function to specify the port on which the server should run, and provide a callback function that outputs the message ‘Server running on port 3000’.

Running the server

To start the server, open the terminal, navigate to the project’s root directory, and execute the following command.

npm start

Once the server is running successfully, the output will match the message we provided in the callback for the listen function. Next, go to the following link to see your server display the message “Hello Server!”.

http://localhost:3000

Congratulations 👏 ! You’ve successfully set up a local server, marking a great step in your journey toward backend development. Before wrapping up this tutorial, let’s try one more experiment: sending HTML from our server. We’ll modify the response string to include HTML code when sending it back from the server.

server.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('<h1>Hello World!</h1>');
})

app.listen(3000, () => console.log('Server running on port 3000'));

End the current session, restart the application, and visit the same URL again.

http://localhost:3000

Yay 🎉 ! The text displays as a heading. Excellent! This marks our initial step in building a backend server. There is much more we can achieve with Node.js and Express. This is just a small start compared to the large-scale real-world projects. Keep learning and growing!


Share with your audience and help them grow too!

About Author

Arslan Mushtaq

A skilled software developer with 10+ years of experience in mobile and web development, proficient in technologies such as React Native, Native Android, and iOS for mobile, and React.js and Next.js for web development. Additional expertise in backend technologies including Node.js, Express.js, Amazon Web Services, and Google Firebase, facilitating collaboration with cross-functional teams across various domains.