Praktyczna Aplikacja: Budowa API z użyciem Node.js, Express i Mongoose

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:

  1. Rejestracja użytkownika: POST /api/users/register
  2. Logowanie użytkownika: POST /api/users/login
  3. Dodanie posta: POST /api/posts
  4. Pobranie postów z paginacją: GET /api/posts?page=1&limit=10

Optymalizacja działania API

  1. Użycie lean() – Dodanie .lean() w zapytaniach, w których nie modyfikujemy danych, zmniejsza obciążenie pamięci.
  2. Indeksy – Tworzenie indeksów na polach, takich jak username i email, poprawia wydajność zapytań.
  3. 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.