阿里云主机折上折
  • 微信号
Current Site:Index > Example of RESTful API development

Example of RESTful API development

Author:Chuan Chen 阅读数:27373人阅读 分类: MongoDB

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.