
Creating an API Using ExpressJS
Rajesh Epili • July 3, 2024
In this blog post, we take a deep dive into creating a robust and secure API using ExpressJS, a powerful and flexible Node.js framework. We'll start by introducing ExpressJS, highlighting its key features and benefits for web and mobile application development. Then, we walk you through a step-by-step guide to setting up an Express server, implementing authentication with JWT, and performing CRUD operations with SQLite as the database. By the end of this tutorial, you'll have a solid foundation for building your own APIs. Don't miss our next blog, where we'll create a frontend to interact with this API, completing the full stack development cycle.
Introduction
In the world of web development, APIs (Application Programming Interfaces) are fundamental building blocks that enable different software applications to communicate with each other. Whether you're building a web app, mobile app, or any other software requiring data exchange, APIs are indispensable. This blog will introduce you to ExpressJS, a powerful and flexible Node.js framework, and guide you through creating a simple API with authentication and CRUD functionality using SQLite as the database.
What is ExpressJS?
ExpressJS is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It is widely used for building APIs due to its simplicity and ease of use. With ExpressJS, you can handle routing, middleware, and HTTP requests and responses efficiently.
Key Features of ExpressJS:
- Fast and Unopinionated: Express provides essential tools for building web applications without enforcing a specific structure or pattern, allowing developers the freedom to design their apps as they see fit.
- Middleware Support: Middleware functions in ExpressJS can be used to handle various tasks such as logging, authentication, and data parsing.
- Routing: ExpressJS provides a robust routing system that can handle complex URL structures and HTTP methods.
- Easy Integration: ExpressJS integrates easily with various templating engines and databases, making it versatile for different types of applications.
Building a Sample API with ExpressJS
Let's create a simple API with authentication and CRUD functionality. We will use SQLite as our database to keep things lightweight and simple.
Prerequisites
Ensure you have Node.js and npm installed on your machine. If not, download and install them from Node.js official website.
Step 1: Setting Up the Project
First, let's initialize a new Node.js project and install the necessary dependencies.
-
Initialize a new Node.js project:
mkdir express-api cd express-api npm init -y -
Install required dependencies:
npm install express sqlite3 body-parser jsonwebtoken bcryptjs
Step 2: Setting Up the Express Server
Create a file named server.js and add the following code to set up the Express server.
const express = require('express'); const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const sqlite3 = require('sqlite3').verbose(); const app = express(); const PORT = 3000; const SECRET_KEY = 'your_secret_key'; app.use(bodyParser.json()); const db = new sqlite3.Database(':memory:');
To generate a secret key, one can use openssl to generate a key. Using the following code one has Base64 key:
openssl rand -base64 64
In this section, we:
- Imported necessary modules.
- Created an Express application.
- Set a port number and a secret key for JWT.
- Added middleware to parse JSON requests.
- Initialized an in-memory SQLite database.
Step 3: Database Setup
Next, we'll set up our database schema.
// Create Users table db.serialize(() => { db.run(`CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT )`); db.run(`CREATE TABLE items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, userId INTEGER, FOREIGN KEY(userId) REFERENCES users(id) )`); });
Here, we create two tables: users and items. The users table stores user credentials, and the items table stores items associated with users.
Step 4: Middleware for Token Verification
We need a middleware function to verify JWT tokens.
// Middleware to verify token const verifyToken = (req, res, next) => { const token = req.headers['authorization'].split(' ')[1]; if (!token) return res.status(403).send('No token provided.'); jwt.verify(token, SECRET_KEY, (err, decoded) => { if (err) return res.status(500).send('Failed to authenticate token.'); req.userId = decoded.id; next(); }); };
This middleware function checks for a token in the request headers and verifies it. If the token is valid, it extracts the user ID and attaches it to the request object.
Step 5: User Registration and Login
We'll implement endpoints for user registration and login.
// Register endpoint app.post('/register', (req, res) => { const { username, password } = req.body; const hashedPassword = bcrypt.hashSync(password, 8); db.run(`INSERT INTO users (username, password) VALUES (?, ?)`, [username, hashedPassword], function(err) { if (err) return res.status(500).send('There was a problem registering the user.'); const token = jwt.sign({ id: this.lastID }, SECRET_KEY, { expiresIn: 86400 }); res.status(200).send({ auth: true, token: token }); }); }); // Login endpoint app.post('/login', (req, res) => { const { username, password } = req.body; db.get(`SELECT * FROM users WHERE username = ?`, [username], (err, user) => { if (err) return res.status(500).send('Error on the server.'); if (!user) return res.status(404).send('No user found.'); const passwordIsValid = bcrypt.compareSync(password, user.password); if (!passwordIsValid) return res.status(401).send({ auth: false, token: null }); const token = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: 86400 }); res.status(200).send({ auth: true, token: token }); }); });
- The register endpoint hashes the user's password and stores the user in the database. It then generates a JWT token and returns it.
- The login endpoint verifies the user's credentials, and if valid, generates and returns a JWT token.
Step 6: CRUD Operations
Now, we'll implement CRUD operations for the items.
Create Item
// Create Item endpoint app.post('/items', verifyToken, (req, res) => { const { name, description } = req.body; const userId = req.userId; db.run(`INSERT INTO items (name, description, userId) VALUES (?, ?, ?)`, [name, description, userId], function(err) { if (err) return res.status(500).send('There was a problem adding the item.'); res.status(201).send({ id: this.lastID, name, description, userId }); }); });
This endpoint creates a new item associated with the authenticated user.
Read Items
// Read Items endpoint app.get('/items', verifyToken, (req, res) => { db.all(`SELECT * FROM items WHERE userId = ?`, [req.userId], (err, rows) => { if (err) return res.status(500).send('There was a problem finding the items.'); res.status(200).send(rows); }); });
This endpoint retrieves all items associated with the authenticated user.
Update Item
// Update Item endpoint app.put('/items/:id', verifyToken, (req, res) => { const { id } = req.params; const { name, description } = req.body; db.run(`UPDATE items SET name = ?, description = ? WHERE id = ? AND userId = ?`, [name, description, id, req.userId], function(err) { if (err) return res.status(500).send('There was a problem updating the item.'); if (this.changes === 0) return res.status(404).send('Item not found or unauthorized.'); res.status(200).send({ id, name, description }); }); });
This endpoint updates an item if it belongs to the authenticated user.
Delete Item
// Delete Item endpoint app.delete('/items/:id', verifyToken, (req, res) => { const { id } = req.params; db.run(`DELETE FROM items WHERE id = ? AND userId = ?`, [id, req.userId], function(err) { if (err) return res.status(500).send('There was a problem deleting the item.'); if (this.changes === 0) return res.status(404).send('Item not found or unauthorized.'); res.status(200).send('Item deleted.'); }); });
This endpoint deletes an item if it belongs to the authenticated user.
Step 7: Starting the Server
Finally, start the Express server.
app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Complete Code
Here is the complete code for your reference:
const express = require('express'); const bodyParser = require('body-parser'); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const sqlite3 = require('sqlite3').verbose(); const app = express(); const PORT = 3000; const SECRET_KEY = 'your_secret_key'; app.use(bodyParser.json()); const db = new sqlite3.Database(':memory:'); // Create Users and Items tables db.serialize(() => { db.run(`CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT )`); db.run(`CREATE TABLE items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, description TEXT, userId INTEGER, FOREIGN KEY(userId) REFERENCES users(id) )`); }); // Middleware to verify token const verifyToken = (req, res, next) => { const token = req.headers['authorization'].split(' ')[1]; if (!token) return res.status(403).send('No token provided.'); jwt.verify(token, SECRET_KEY, (err, decoded) => { if (err) return res.status(500).send('Failed to authenticate token.'); req.userId = decoded.id; next(); }); }; // Register endpoint app.post('/register', (req, res) => { const { username, password } = req.body; const hashedPassword = bcrypt.hashSync(password, 8); db.run(`INSERT INTO users (username, password) VALUES (?, ?)`, [username, hashedPassword], function(err) { if (err) return res.status(500).send('There was a problem registering the user.'); const token = jwt.sign({ id: this.lastID }, SECRET_KEY, { expiresIn: 86400 }); res.status(200).send({ auth: true, token: token }); }); }); // Login endpoint app.post('/login', (req, res) => { const { username, password } = req.body; db.get(`SELECT * FROM users WHERE username = ?`, [username], (err, user) => { if (err) return res.status(500).send('Error on the server.'); if (!user) return res.status(404).send('No user found.'); const passwordIsValid = bcrypt.compareSync(password, user.password); if (!passwordIsValid) return res.status(401).send({ auth: false, token: null }); const token = jwt.sign({ id: user.id }, SECRET_KEY, { expiresIn: 86400 }); res.status(200).send({ auth: true, token: token }); }); }); // Create Item endpoint app.post('/items', verifyToken, (req, res) => { const { name, description } = req.body; const userId = req.userId; db.run(`INSERT INTO items (name, description, userId) VALUES (?, ?, ?)`, [name, description, userId], function(err) { if (err) return res.status(500).send('There was a problem adding the item.'); res.status(201).send({ id: this.lastID, name, description, userId }); }); }); // Read Items endpoint app.get('/items', verifyToken, (req, res) => { db.all(`SELECT * FROM items WHERE userId = ?`, [req.userId], (err, rows) => { if (err) return res.status(500).send('There was a problem finding the items.'); res.status(200).send(rows); }); }); // Update Item endpoint app.put('/items/:id', verifyToken, (req, res) => { const { id } = req.params; const { name, description } = req.body; db.run(`UPDATE items SET name = ?, description = ? WHERE id = ? AND userId = ?`, [name, description, id, req.userId], function(err) { if (err) return res.status(500).send('There was a problem updating the item.'); if (this.changes === 0) return res.status(404).send('Item not found or unauthorized.'); res.status(200).send({ id, name, description }); }); }); // Delete Item endpoint app.delete('/items/:id', verifyToken, (req, res) => { const { id } = req.params; db.run(`DELETE FROM items WHERE id = ? AND userId = ?`, [id, req.userId], function(err) { if (err) return res.status(500).send('There was a problem deleting the item.'); if (this.changes === 0) return res.status(404).send('Item not found or unauthorized.'); res.status(200).send('Item deleted.'); }); }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Step 3: Testing the API
You can test the API endpoints using a tool like Postman or curl.
- Register a new user:
- URL:
http://localhost:3000/register - Method:
POST - Body:
{"username": "testuser", "password": "testpassword"}
- URL:
- Login to get a token:
- URL:
http://localhost:3000/login - Method:
POST - Body:
{"username": "testuser", "password": "testpassword"}
- URL:
- Create a new item:
- URL:
http://localhost:3000/items - Method:
POST - Headers:
Authorization:<token> - Body:
{"name": "Item 1", "description": "This is item 1"}
- URL:
- Get all items:
- URL:
http://localhost:3000/items - Method:
GET - Headers:
Authorization:<token>
- URL:
- Update an item:
- URL:
http://localhost:3000/items/1 - Method:
PUT - Headers:
Authorization:<token> - Body:
{"name": "Updated Item 1", "description": "This is the updated item 1"}
- URL:
- Delete an item:
- URL:
http://localhost:3000/items/1 - Method:
DELETE - Headers:
Authorization:<token>
- URL:
Conclusion
In this blog, we explored how to create a simple API using ExpressJS. We covered setting up the Express server, adding authentication with JWT, and implementing CRUD operations using SQLite as the database. ExpressJS provides a powerful yet straightforward way to build APIs, making it a popular choice for many developers. With this foundational knowledge, you can further expand and enhance your API to suit more complex requirements.
Stay tuned for our next blog, where we'll dive into creating a frontend for this API, enabling an interactive user experience.