Example of RESTful API development
RESTful API is an API design style based on the HTTP protocol, which utilizes HTTP methods (GET, POST, PUT, DELETE, etc.) to perform operations on resources. Mongoose, as a MongoDB object modeling tool for Node.js, simplifies database operations and is highly suitable for building RESTful APIs.
Environment Setup and Basic Configuration
First, install the necessary dependencies:
npm install express mongoose body-parser
Create a basic Express application and connect to MongoDB:
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const app = express();
// Middleware configuration
app.use(bodyParser.json());
// Database connection
mongoose.connect('mongodb://localhost:27017/restapi', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', () => {
console.log('Connected to MongoDB');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Defining Mongoose Models
For user management, create a user model:
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
const User = mongoose.model('User', userSchema);
Implementing CRUD Operations
Create User (POST)
app.post('/api/users', async (req, res) => {
try {
const { username, email, password } = req.body;
const newUser = new User({
username,
email,
password
});
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Get User List (GET)
app.get('/api/users', async (req, res) => {
try {
const users = await User.find().select('-password');
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Get Single User (GET)
app.get('/api/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Update User (PUT)
app.put('/api/users/:id', async (req, res) => {
try {
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
).select('-password');
if (!updatedUser) {
return res.status(404).json({ message: 'User not found' });
}
res.json(updatedUser);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
Delete User (DELETE)
app.delete('/api/users/:id', async (req, res) => {
try {
const deletedUser = await User.findByIdAndDelete(req.params.id);
if (!deletedUser) {
return res.status(404).json({ message: 'User not found' });
}
res.json({ message: 'User deleted successfully' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Advanced Query Features
Pagination
app.get('/api/users', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const users = await User.find()
.select('-password')
.skip(skip)
.limit(limit);
const total = await User.countDocuments();
res.json({
data: users,
meta: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Sorting and Filtering
app.get('/api/users', async (req, res) => {
try {
const { sort, username, email } = req.query;
const query = {};
if (username) query.username = new RegExp(username, 'i');
if (email) query.email = new RegExp(email, 'i');
const sortOption = sort === 'desc' ? '-createdAt' : 'createdAt';
const users = await User.find(query)
.select('-password')
.sort(sortOption);
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Data Validation and Error Handling
Mongoose provides built-in validation:
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Username is required'],
minlength: [3, 'Username must be at least 3 characters'],
maxlength: [30, 'Username cannot exceed 30 characters']
},
email: {
type: String,
required: [true, 'Email is required'],
unique: true,
match: [/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 'Please fill a valid email address']
},
// Other fields...
});
Custom error handling middleware:
app.use((err, req, res, next) => {
if (err.name === 'ValidationError') {
const errors = {};
for (const field in err.errors) {
errors[field] = err.errors[field].message;
}
return res.status(400).json({ errors });
}
if (err.code === 11000) {
return res.status(400).json({ error: 'Duplicate field value entered' });
}
res.status(500).json({ error: 'Something went wrong' });
});
Handling Related Data
Add a post model and associate it with users:
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
content: {
type: String,
required: true
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
createdAt: {
type: Date,
default: Date.now
}
});
const Post = mongoose.model('Post', postSchema);
Get a user and their posts:
app.get('/api/users/:id/posts', async (req, res) => {
try {
const userWithPosts = await User.findById(req.params.id)
.populate('posts')
.select('-password');
if (!userWithPosts) {
return res.status(404).json({ message: 'User not found' });
}
res.json(userWithPosts);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Performance Optimization
Index Optimization
userSchema.index({ username: 1 }, { unique: true });
userSchema.index({ email: 1 }, { unique: true });
postSchema.index({ author: 1 });
Query Optimization
app.get('/api/posts', async (req, res) => {
try {
const posts = await Post.find()
.populate('author', 'username email -_id')
.lean()
.exec();
res.json(posts);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
Security Considerations
Password Encryption
const bcrypt = require('bcryptjs');
const saltRounds = 10;
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
this.password = await bcrypt.hash(this.password, saltRounds);
next();
} catch (error) {
next(error);
}
});
Sensitive Data Filtering
userSchema.methods.toJSON = function() {
const user = this.toObject();
delete user.password;
delete user.__v;
return user;
};
Testing API Endpoints
Using Jest for testing:
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');
describe('User API', () => {
beforeEach(async () => {
await User.deleteMany({});
});
it('should create a new user', async () => {
const res = await request(app)
.post('/api/users')
.send({
username: 'testuser',
email: 'test@example.com',
password: 'password123'
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('_id');
expect(res.body.username).toBe('testuser');
});
it('should not create user with duplicate email', async () => {
await User.create({
username: 'existing',
email: 'test@example.com',
password: 'password123'
});
const res = await request(app)
.post('/api/users')
.send({
username: 'testuser',
email: 'test@example.com',
password: 'password123'
});
expect(res.statusCode).toEqual(400);
});
});
Deployment Considerations
Environment Variable Configuration
Create a .env
file:
MONGODB_URI=mongodb://localhost:27017/restapi
PORT=3000
JWT_SECRET=your_secret_key
Modify application configuration:
require('dotenv').config();
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Production Environment Middleware
if (process.env.NODE_ENV === 'production') {
app.use(require('helmet')());
app.use(require('compression')());
app.use(require('cors')({
origin: ['https://yourdomain.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
}
Version Control
Implement API versioning:
// routes/v1/users.js
const express = require('express');
const router = express.Router();
const User = require('../../models/User');
router.get('/', async (req, res) => {
// v1 version of get user logic
});
module.exports = router;
// routes/v2/users.js
const express = require('express');
const router = express.Router();
const User = require('../../models/User');
router.get('/', async (req, res) => {
// v2 version of get user logic
});
module.exports = router;
// app.js
app.use('/api/v1/users', require('./routes/v1/users'));
app.use('/api/v2/users', require('./routes/v2/users'));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:与TypeScript的结合使用
下一篇:与Express.js的集成