Build a full-featured REST API for managing students with CRUD operations.
student-api/
|-- package.json
|-- .env
|-- server.js # Main application entry
|-- config/
| |-- database.js # Database connection
|-- models/
| |-- Student.js # Student model
|-- routes/
| |-- students.js # Student routes
|-- middleware/
|-- errorHandler.js
/api/students
- Get all students (with pagination)
/api/students/:id
- Get single student by ID
/api/students
- Create new student
/api/students/:id
- Update student
/api/students/:id
- Delete student
{
"name": "student-api",
"version": "1.0.0",
"description": "REST API for student management",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mysql2": "^3.6.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Install dependencies:
npm install
# Database Configuration
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=school
DB_CONNECTION_LIMIT=10
# Server Configuration
PORT=3000
NODE_ENV=development
// config/database.js
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: parseInt(process.env.DB_CONNECTION_LIMIT) || 10,
queueLimit: 0
});
// Test connection on startup
pool.getConnection()
.then(connection => {
console.log('Database connected successfully');
connection.release();
})
.catch(err => {
console.error('Database connection failed:', err.message);
process.exit(1);
});
module.exports = pool;
// models/Student.js
const pool = require('../config/database');
class Student {
// Get all students with pagination
static async findAll(page = 1, limit = 10) {
const offset = (page - 1) * limit;
// Get total count
const [countResult] = await pool.execute(
'SELECT COUNT(*) as total FROM students'
);
const total = countResult[0].total;
// Get paginated data
const [rows] = await pool.execute(
'SELECT * FROM students ORDER BY id DESC LIMIT ? OFFSET ?',
[limit, offset]
);
return {
data: rows,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / limit)
}
};
}
// Get single student by ID
static async findById(id) {
const [rows] = await pool.execute(
'SELECT * FROM students WHERE id = ?',
[id]
);
return rows[0] || null;
}
// Get student by email
static async findByEmail(email) {
const [rows] = await pool.execute(
'SELECT * FROM students WHERE email = ?',
[email]
);
return rows[0] || null;
}
// Create new student
static async create(studentData) {
const { name, email, age, grade } = studentData;
const [result] = await pool.execute(
`INSERT INTO students (name, email, age, grade)
VALUES (?, ?, ?, ?)`,
[name, email, age, grade]
);
return {
id: result.insertId,
name,
email,
age,
grade
};
}
// Update student
static async update(id, studentData) {
const { name, email, age, grade } = studentData;
const [result] = await pool.execute(
`UPDATE students
SET name = ?, email = ?, age = ?, grade = ?
WHERE id = ?`,
[name, email, age, grade, id]
);
if (result.affectedRows === 0) {
return null;
}
return { id, name, email, age, grade };
}
// Delete student
static async delete(id) {
const [result] = await pool.execute(
'DELETE FROM students WHERE id = ?',
[id]
);
return result.affectedRows > 0;
}
// Search students
static async search(query) {
const [rows] = await pool.execute(
`SELECT * FROM students
WHERE name LIKE ? OR email LIKE ?
ORDER BY name`,
[`%${query}%`, `%${query}%`]
);
return rows;
}
}
module.exports = Student;
// middleware/errorHandler.js
// Custom error class
class ApiError extends Error {
constructor(statusCode, message) {
super(message);
this.statusCode = statusCode;
}
}
// Error handling middleware
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
// Handle known API errors
if (err instanceof ApiError) {
return res.status(err.statusCode).json({
success: false,
error: err.message
});
}
// Handle MySQL duplicate entry error
if (err.code === 'ER_DUP_ENTRY') {
return res.status(400).json({
success: false,
error: 'Duplicate entry. This email already exists.'
});
}
// Handle MySQL foreign key error
if (err.code === 'ER_NO_REFERENCED_ROW_2') {
return res.status(400).json({
success: false,
error: 'Referenced record not found.'
});
}
// Generic server error
res.status(500).json({
success: false,
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message
});
};
module.exports = { ApiError, errorHandler };
// routes/students.js
const express = require('express');
const router = express.Router();
const Student = require('../models/Student');
const { ApiError } = require('../middleware/errorHandler');
// GET /api/students - Get all students
router.get('/', async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
// Search functionality
if (req.query.search) {
const students = await Student.search(req.query.search);
return res.json({
success: true,
count: students.length,
data: students
});
}
const result = await Student.findAll(page, limit);
res.json({
success: true,
...result
});
} catch (error) {
next(error);
}
});
// GET /api/students/:id - Get single student
router.get('/:id', async (req, res, next) => {
try {
const student = await Student.findById(req.params.id);
if (!student) {
throw new ApiError(404, 'Student not found');
}
res.json({
success: true,
data: student
});
} catch (error) {
next(error);
}
});
// POST /api/students - Create student
router.post('/', async (req, res, next) => {
try {
const { name, email, age, grade } = req.body;
// Validation
if (!name || !email) {
throw new ApiError(400, 'Name and email are required');
}
// Check if email exists
const existing = await Student.findByEmail(email);
if (existing) {
throw new ApiError(400, 'Email already registered');
}
const student = await Student.create({ name, email, age, grade });
res.status(201).json({
success: true,
message: 'Student created successfully',
data: student
});
} catch (error) {
next(error);
}
});
// PUT /api/students/:id - Update student
router.put('/:id', async (req, res, next) => {
try {
const { name, email, age, grade } = req.body;
// Check if student exists
const existing = await Student.findById(req.params.id);
if (!existing) {
throw new ApiError(404, 'Student not found');
}
// Check if email is being changed to an existing one
if (email && email !== existing.email) {
const emailExists = await Student.findByEmail(email);
if (emailExists) {
throw new ApiError(400, 'Email already in use');
}
}
const student = await Student.update(req.params.id, {
name: name || existing.name,
email: email || existing.email,
age: age !== undefined ? age : existing.age,
grade: grade || existing.grade
});
res.json({
success: true,
message: 'Student updated successfully',
data: student
});
} catch (error) {
next(error);
}
});
// DELETE /api/students/:id - Delete student
router.delete('/:id', async (req, res, next) => {
try {
const student = await Student.findById(req.params.id);
if (!student) {
throw new ApiError(404, 'Student not found');
}
await Student.delete(req.params.id);
res.json({
success: true,
message: 'Student deleted successfully',
data: { id: req.params.id }
});
} catch (error) {
next(error);
}
});
module.exports = router;
// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const studentRoutes = require('./routes/students');
const { errorHandler } = require('./middleware/errorHandler');
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Request logging (development)
if (process.env.NODE_ENV !== 'production') {
app.use((req, res, next) => {
console.log(`${req.method} ${req.path}`);
next();
});
}
// Routes
app.use('/api/students', studentRoutes);
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({
success: false,
error: 'Endpoint not found'
});
});
// Error handler (must be last)
app.use(errorHandler);
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});
Get all students:
curl http://localhost:3000/api/students
Get single student:
curl http://localhost:3000/api/students/1
Create student:
curl -X POST http://localhost:3000/api/students \
-H "Content-Type: application/json" \
-d '{"name": "John Doe", "email": "john@school.com", "age": 20, "grade": "A"}'
Update student:
curl -X PUT http://localhost:3000/api/students/1 \
-H "Content-Type: application/json" \
-d '{"name": "John Smith", "grade": "A+"}'
Delete student:
curl -X DELETE http://localhost:3000/api/students/1
Search students:
curl "http://localhost:3000/api/students?search=john"
Pagination:
curl "http://localhost:3000/api/students?page=2&limit=5"
-- Run this SQL to set up the database
CREATE DATABASE IF NOT EXISTS school;
USE school;
CREATE TABLE IF NOT EXISTS students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE NOT NULL,
age INT,
grade VARCHAR(10),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert sample data
INSERT INTO students (name, email, age, grade) VALUES
('Alice Johnson', 'alice@school.com', 20, 'A'),
('Bob Smith', 'bob@school.com', 22, 'B+'),
('Charlie Brown', 'charlie@school.com', 21, 'A-'),
('Diana Prince', 'diana@school.com', 19, 'A+'),
('Edward Norton', 'edward@school.com', 23, 'B');