Tworzenie API w oparciu o Node.js, Express i Mongoose to popularne podejście do budowania aplikacji, które są skalowalne i wydajne. W tym artykule stworzymy proste RESTful API, użyjemy funkcji Mongoose, a także omówimy, jak testować i optymalizować jego działanie.
Założenia projektu
Nasze API będzie umożliwiać zarządzanie użytkownikami (User) i ich postami (Post). API będzie wspierać operacje CRUD (Create, Read, Update, Delete) oraz bardziej zaawansowane funkcje Mongoose, takie jak walidacja, agregacja i paginacja.
1. Konfiguracja projektu
Na początek utwórzmy katalog projektu i zainstalujmy potrzebne pakiety:
mkdir api-project
cd api-project
npm init -y
npm install express mongoose dotenv body-parser bcrypt
- express: Framework do tworzenia serwera HTTP.
- mongoose: Biblioteka do zarządzania MongoDB.
- dotenv: Zarządzanie zmiennymi środowiskowymi.
- body-parser: Umożliwia przetwarzanie danych z zapytań POST.
- bcrypt: Hashowanie haseł.
Utwórz plik .env, aby przechowywać konfigurację bazy danych:
MONGODB_URI=mongodb://localhost:27017/apiProject
PORT=3000
2. Połączenie z MongoDB i konfiguracja Express
Najpierw utwórzmy plik app.js, aby skonfigurować połączenie z MongoDB oraz uruchomić serwer.
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
require('dotenv').config();
const app = express();
app.use(bodyParser.json());
mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Połączono z MongoDB'))
.catch(error => console.log('Błąd połączenia:', error));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Serwer działa na porcie ${PORT}`));
3. Tworzenie Modeli: User i Post
Model User
Utwórzmy model User z walidacją i hashowaniem hasła przy pomocy pluginu Mongoose.
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true, minlength: 3 },
email: { type: String, required: true, unique: true },
password: { type: String, required: true, minlength: 8 }
}, { timestamps: true });
// Hashowanie hasła przed zapisem
userSchema.pre('save', async function (next) {
if (this.isModified('password')) {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
}
next();
});
const User = mongoose.model('User', userSchema);
module.exports = User;
Model Post
Utworzymy model Post, który będzie powiązany z użytkownikiem poprzez referencję.
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
content: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }
}, { timestamps: true });
const Post = mongoose.model('Post', postSchema);
module.exports = Post;
4. Tworzenie Podstawowych Tras API
Stworzymy proste trasy do zarządzania użytkownikami i postami, umożliwiające operacje CRUD.
Trasy dla User
W pliku routes/user.js:
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const bcrypt = require('bcrypt');
// Rejestracja użytkownika
router.post('/register', async (req, res) => {
try {
const newUser = new User(req.body);
await newUser.save();
res.status(201).json(newUser);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Logowanie użytkownika
router.post('/login', async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user || !(await bcrypt.compare(req.body.password, user.password))) {
return res.status(401).json({ error: 'Nieprawidłowe dane logowania' });
}
res.json({ message: 'Zalogowano pomyślnie' });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Pobieranie wszystkich użytkowników
router.get('/', async (req, res) => {
try {
const users = await User.find().lean();
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
Trasy dla Post
W pliku routes/post.js:
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
// Tworzenie posta
router.post('/', async (req, res) => {
try {
const newPost = new Post(req.body);
await newPost.save();
res.status(201).json(newPost);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// Pobieranie wszystkich postów z paginacją
router.get('/', async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
try {
const posts = await Post.find()
.skip((page - 1) * limit)
.limit(limit)
.populate('author', 'username')
.lean();
res.json(posts);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;
W app.js dodajemy trasy:
const userRoutes = require('./routes/user');
const postRoutes = require('./routes/post');
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
5. Testowanie i Optymalizacja API
Testowanie API
Do testowania API można użyć narzędzi takich jak Postman lub Insomnia. Przykłady:
- Rejestracja użytkownika: POST
/api/users/register - Logowanie użytkownika: POST
/api/users/login - Dodanie posta: POST
/api/posts - Pobranie postów z paginacją: GET
/api/posts?page=1&limit=10
Optymalizacja działania API
- Użycie
lean()– Dodanie.lean()w zapytaniach, w których nie modyfikujemy danych, zmniejsza obciążenie pamięci. - Indeksy – Tworzenie indeksów na polach, takich jak
usernameiemail, poprawia wydajność zapytań. - Paginacja – Ograniczenie liczby wyników za pomocą paginacji przyspiesza przetwarzanie danych.
User.schema.index({ email: 1 });
Post.schema.index({ author: 1, title: 1 });
Obsługa błędów
Ważne jest również implementowanie globalnego middleware do obsługi błędów w Express. W app.js:
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Coś poszło nie tak!' });
});
Podsumowanie
W tym artykule stworzyliśmy podstawowe RESTful API z użyciem Node.js, Express i Mongoose. Omówiliśmy:
- Modele i trasy do zarządzania użytkownikami i postami,
- Walidację i zabezpieczenia, takie jak hashowanie haseł,
- Optymalizację zapytań poprzez
lean()i paginację.
Dzięki tym krokom masz gotowe podstawy, aby rozbudować aplikację o dodatkowe funkcje i wdrożyć ją do produkcji.