In this case study, we will design and develop a RESTful API for a social network. This API will allow users to perform various actions such as creating profiles, posting updates, following other users, and liking posts. We will cover the following key aspects:
- Defining Resources and Endpoints
- Designing the Database Schema
- Implementing the API Endpoints
- Handling Authentication and Authorization
- Testing the API
- Defining Resources and Endpoints
Resources
The main resources for our social network API are:
- Users: Represents the users of the social network.
- Posts: Represents the posts created by users.
- Followers: Represents the relationship between users (who follows whom).
- Likes: Represents the likes on posts.
Endpoints
Here are the endpoints we will implement:
Resource | Endpoint | HTTP Method | Description |
---|---|---|---|
Users | /users | GET | Retrieve a list of users |
Users | /users | POST | Create a new user |
Users | /users/{id} | GET | Retrieve a specific user |
Users | /users/{id} | PUT | Update a specific user |
Users | /users/{id} | DELETE | Delete a specific user |
Posts | /posts | GET | Retrieve a list of posts |
Posts | /posts | POST | Create a new post |
Posts | /posts/{id} | GET | Retrieve a specific post |
Posts | /posts/{id} | PUT | Update a specific post |
Posts | /posts/{id} | DELETE | Delete a specific post |
Followers | /users/{id}/followers | GET | Retrieve a list of followers for a user |
Followers | /users/{id}/follow | POST | Follow a user |
Followers | /users/{id}/unfollow | POST | Unfollow a user |
Likes | /posts/{id}/likes | GET | Retrieve a list of likes for a post |
Likes | /posts/{id}/like | POST | Like a post |
Likes | /posts/{id}/unlike | POST | Unlike a post |
- Designing the Database Schema
We will use a relational database for this case study. Below is the schema for our database:
Users Table
Column | Type | Description |
---|---|---|
id | INT | Primary key |
username | VARCHAR | Unique username |
VARCHAR | Unique email address | |
password | VARCHAR | Hashed password |
created_at | DATETIME | Timestamp of creation |
Posts Table
Column | Type | Description |
---|---|---|
id | INT | Primary key |
user_id | INT | Foreign key to Users |
content | TEXT | Content of the post |
created_at | DATETIME | Timestamp of creation |
Followers Table
Column | Type | Description |
---|---|---|
id | INT | Primary key |
user_id | INT | Foreign key to Users |
follower_id | INT | Foreign key to Users |
Likes Table
Column | Type | Description |
---|---|---|
id | INT | Primary key |
post_id | INT | Foreign key to Posts |
user_id | INT | Foreign key to Users |
- Implementing the API Endpoints
Setting Up the Development Environment
We will use Node.js and Express for our API. First, set up a new Node.js project and install the necessary dependencies:
Creating a Basic Server
Create a file named server.js
and set up a basic Express server:
const express = require('express'); const bodyParser = require('body-parser'); const mongoose = require('mongoose'); const app = express(); app.use(bodyParser.json()); mongoose.connect('mongodb://localhost:27017/socialnetwork', { useNewUrlParser: true, useUnifiedTopology: true }); app.listen(3000, () => { console.log('Server is running on port 3000'); });
Defining Models
Create a directory named models
and define the models for Users, Posts, Followers, and Likes.
User Model (models/User.js
)
const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ username: { type: String, required: true, unique: true }, email: { type: String, required: true, unique: true }, password: { type: String, required: true }, created_at: { type: Date, default: Date.now } }); module.exports = mongoose.model('User', userSchema);
Post Model (models/Post.js
)
const mongoose = require('mongoose'); const postSchema = new mongoose.Schema({ user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, content: { type: String, required: true }, created_at: { type: Date, default: Date.now } }); module.exports = mongoose.model('Post', postSchema);
Follower Model (models/Follower.js
)
const mongoose = require('mongoose'); const followerSchema = new mongoose.Schema({ user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, follower_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true } }); module.exports = mongoose.model('Follower', followerSchema);
Like Model (models/Like.js
)
const mongoose = require('mongoose'); const likeSchema = new mongoose.Schema({ post_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true }, user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true } }); module.exports = mongoose.model('Like', likeSchema);
Implementing Endpoints
User Endpoints
Create a directory named routes
and define the routes for users in routes/users.js
:
const express = require('express'); const router = express.Router(); const User = require('../models/User'); // Get all users router.get('/', async (req, res) => { try { const users = await User.find(); res.json(users); } catch (err) { res.status(500).json({ message: err.message }); } }); // Create a new user router.post('/', async (req, res) => { const user = new User({ username: req.body.username, email: req.body.email, password: req.body.password }); try { const newUser = await user.save(); res.status(201).json(newUser); } catch (err) { res.status(400).json({ message: err.message }); } }); // Get a specific user router.get('/:id', getUser, (req, res) => { res.json(res.user); }); // Update a specific user router.put('/:id', getUser, async (req, res) => { if (req.body.username != null) { res.user.username = req.body.username; } if (req.body.email != null) { res.user.email = req.body.email; } if (req.body.password != null) { res.user.password = req.body.password; } try { const updatedUser = await res.user.save(); res.json(updatedUser); } catch (err) { res.status(400).json({ message: err.message }); } }); // Delete a specific user router.delete('/:id', getUser, async (req, res) => { try { await res.user.remove(); res.json({ message: 'Deleted User' }); } catch (err) { res.status(500).json({ message: err.message }); } }); // Middleware to get user by ID async function getUser(req, res, next) { let user; try { user = await User.findById(req.params.id); if (user == null) { return res.status(404).json({ message: 'Cannot find user' }); } } catch (err) { return res.status(500).json({ message: err.message }); } res.user = user; next(); } module.exports = router;
Integrating Routes
In server.js
, integrate the user routes:
Repeat similar steps for implementing the routes for Posts, Followers, and Likes.
- Handling Authentication and Authorization
For authentication, we will use JSON Web Tokens (JWT). Install the necessary packages:
User Registration and Login
Update the user routes to include registration and login:
const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); // Register a new user router.post('/register', async (req, res) => { const hashedPassword = await bcrypt.hash(req.body.password, 10); const user = new User({ username: req.body.username, email: req.body.email, password: hashedPassword }); try { const newUser = await user.save(); res.status(201).json(newUser); } catch (err) { res.status(400).json({ message: err.message }); } }); // Login a user router.post('/login', async (req, res) => { const user = await User.findOne({ email: req.body.email }); if (user == null) { return res.status(400).json({ message: 'Cannot find user' }); } try { if (await bcrypt.compare(req.body.password, user.password)) { const token = jwt.sign({ id: user._id }, 'SECRET_KEY'); res.json({ token }); } else { res.status(400).json({ message: 'Invalid credentials' }); } } catch (err) { res.status(500).json({ message: err.message }); } });
Protecting Routes
Create a middleware to protect routes:
function authenticateToken(req, res, next) { const token = req.header('Authorization'); if (!token) return res.status(401).json({ message: 'Access Denied' }); try { const verified = jwt.verify(token, 'SECRET_KEY'); req.user = verified; next(); } catch (err) { res.status(400).json({ message: 'Invalid Token' }); } }
Use this middleware to protect routes that require authentication.
- Testing the API
Use Postman to test the API endpoints. Create a collection and add requests for each endpoint. Ensure that you test both successful and error scenarios.
Conclusion
In this case study, we designed and developed a RESTful API for a social network. We covered defining resources and endpoints, designing the database schema, implementing the API endpoints, handling authentication and authorization, and testing the API. This comprehensive example provides a solid foundation for building more complex APIs in the future.
REST API Course: Principles of Design and Development of RESTful APIs
Module 1: Introduction to RESTful APIs
Module 2: Design of RESTful APIs
- Principles of RESTful API Design
- Resources and URIs
- HTTP Methods
- HTTP Status Codes
- API Versioning
- API Documentation
Module 3: Development of RESTful APIs
- Setting Up the Development Environment
- Creating a Basic Server
- Handling Requests and Responses
- Authentication and Authorization
- Error Handling
- Testing and Validation
Module 4: Best Practices and Security
- Best Practices in API Design
- Security in RESTful APIs
- Rate Limiting and Throttling
- CORS and Security Policies
Module 5: Tools and Frameworks
- Postman for API Testing
- Swagger for Documentation
- Popular Frameworks for RESTful APIs
- Continuous Integration and Deployment