RU
UAH

Простой REST API на Node.js: GET/POST/DELETE

Что делает пример

Blog Эндпоинты:

  • GET /api/items — список заметок/задач
  • POST /api/items — создать запись
  • DELETE /api/items/:id — удалить запись по ID

Хранение: файл data.json (подходит для shared).
Безопасность: rate-limit, простая проверка токена (опционально).
Сервисные маршруты: /health (проверка), / (краткая подсказка).

Структура проекта

rest-api/
+-- package.json
+-- app.js
+-- storage.js # работа с файлом data.json
+-- data.json # создастся автоматически при первом запуске
+-- .env # опционально для локального запуска

package.json


{
  "name": "rest-api",
  "version": "1.0.0",
  "description": "Simple REST API (notes/todos) for shared hosting (ISPmanager).",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "start:prod": "NODE_ENV=production node app.js"
  },
  "engines": {
    "node": ">=18.x"
  },
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "express-rate-limit": "^7.4.0",
    "helmet": "^7.1.0",
    "morgan": "^1.10.0",
    "nanoid": "^5.0.7"
  }
}
	
	

storage.js — мини-хранилище в файле


// storage.js
const fs = require('fs');
const path = require('path');

const DATA_PATH = path.join(__dirname, 'data.json');

function readAll() {
  if (!fs.existsSync(DATA_PATH)) {
    fs.writeFileSync(DATA_PATH, JSON.stringify([], null, 2));
  }
  const raw = fs.readFileSync(DATA_PATH, 'utf8');
  try { return JSON.parse(raw); } catch {
    // если файл поврежден — перезапишем пустым массивом
    fs.writeFileSync(DATA_PATH, JSON.stringify([], null, 2));
    return [];
  }
}

function writeAll(items) {
  fs.writeFileSync(DATA_PATH, JSON.stringify(items, null, 2));
}

module.exports = { readAll, writeAll, DATA_PATH };

	
	

app.js — сервер и маршруты API


// app.js
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const { nanoid } = require('nanoid');
const { readAll, writeAll } = require('./storage');

const app = express();
const PORT = process.env.PORT || 3000;
const isProd = process.env.NODE_ENV === 'production';

// БЕЗОПАСНОСТЬ, ЛОГИ, ПАРСИНГ
app.use(helmet());
app.use(morgan(isProd ? 'combined' : 'dev'));
app.use(express.json()); // API принимает JSON

// ПРОСТОЙ CORS (если будете обращаться из браузера)
app.use((req, res, next) => {
  const origin = req.headers.origin || '*';
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Vary', 'Origin');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,DELETE,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

// RATE-LIMIT (анти-спам)
const apiLimiter = rateLimit({
  windowMs: 60 * 1000,
  max: 60, // 60 запросов в минуту на IP
  standardHeaders: true,
  legacyHeaders: false
});
app.use('/api/', apiLimiter);

// ОПЦИОНАЛЬНЫЙ ТОКЕН (задайте API_TOKEN в окружении)
// Пример: Authorization: Bearer my-secret-token
function authIfEnabled(req, res, next) {
  const token = process.env.API_TOKEN;
  if (!token) return next(); // токен не настроен — пропускаем
  const header = req.headers.authorization || '';
  const provided = header.startsWith('Bearer ') ? header.slice(7) : '';
  if (provided && provided === token) return next();
  return res.status(401).json({ error: 'Unauthorized' });
}

// INDEX/HEALTH
app.get('/', (req, res) => {
  res.type('text/plain').send([
    'Simple REST API (notes/todos)',
    'GET    /api/items',
    'POST   /api/items   { "title": "...", "done": false }',
    'DELETE /api/items/:id',
    'GET    /health'
  ].join('\n'));
});

app.get('/health', (req, res) => res.json({ status: 'ok', uptime: process.uptime() }));

// ---- API ----

// Список
app.get('/api/items', authIfEnabled, (req, res) => {
  const items = readAll();
  res.json(items);
});

// Создание
app.post('/api/items', authIfEnabled, (req, res) => {
  const { title = '', done = false } = req.body || {};
  const t = String(title).trim();
  if (t.length < 1 || t.length > 200) {
    return res.status(400).json({ error: 'Title must be 1..200 chars' });
  }
  const items = readAll();
  const item = {
    id: nanoid(12),
    title: t,
    done: Boolean(done),
    createdAt: Date.now()
  };
  items.unshift(item); // добавим в начало
  writeAll(items);
  res.status(201).json(item);
});

// Удаление
app.delete('/api/items/:id', authIfEnabled, (req, res) => {
  const { id } = req.params;
  const items = readAll();
  const index = items.findIndex(i => i.id === id);
  if (index === -1) {
    return res.status(404).json({ error: 'Not found' });
  }
  const [removed] = items.splice(index, 1);
  writeAll(items);
  res.json({ removed });
});

// 404 и 500
app.use((req, res) => res.status(404).json({ error: 'Not Found' }));
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ error: 'Internal Server Error' });
});

app.listen(PORT, () => {
  console.log(`REST API running on port ${PORT}`);
});
  
  

.env (для локального запуска)


PORT=3000
NODE_ENV=development

# Опционально — включит защиту токеном:
# API_TOKEN=my-secret-token
  
  

В продакшене (ISPmanager) задайте переменные в панели. Файл .env можно не загружать.

Node.js
Node.js Хостинг
Запусти проект за пару кликов!
Бесплатный SSLСовременные серверы7 дней теста бесплатно
Перейти к тарифам

Локальный запуск


npm install
npm start
# или
npm run start:prod
Проверка:
curl http://localhost:3000/health
curl http://localhost:3000/api/items
curl -X POST http://localhost:3000/api/items \
  -H "Content-Type: application/json" \
  -d '{"title":"Первая заметка","done":false}'
curl -X DELETE http://localhost:3000/api/items/
Если включили токен (API_TOKEN=...), добавляйте заголовок:
-H "Authorization: Bearer my-secret-token"
  
  

Деплой в ISPmanager на shared-хостинге

  • Создайте домен/сайт в ISPmanager
  • Загрузите проект в каталог сайта
  • В терминале/SSH: npm install
  • В разделе Node.js укажите:
    • Версию Node.js (LTS)
    • Старт: app.js или команда npm start
    • Переменные окружения (опционально): NODE_ENV=production, API_TOKEN=...,
    • Привяжите приложение к домену/поддомену (панель сама настроит прокси)
  • Включите HTTPS (Let’s Encrypt) на домене
  • Проверьте: https://ваш-домен/api/items.

Готовые примеры запросов (cURL)

Получить список

curl -s https://ваш-домен/api/items

Добавить запись

  
curl -s -X POST https://ваш-домен/api/items \
  -H "Content-Type: application/json" \
  -d '{"title":"Купить хостинг","done":false}'
    
  

Удалить запись


  curl -s -X DELETE https://ваш-домен/api/items/ID_ИЗ_СПИСКА

Если включён токен, добавляйте: -H "Authorization: Bearer my-secret-token".

Частые вопросы

Для мини-API, демо и MVP — да. Для серьёзных проектов переходите на БД (MySQL/PostgreSQL) или на VPS.
Добавьте маршрут PATCH /api/items/:id и изменяйте title/done по схеме, схожей с POST.
Считывайте параметры ?limit=…&offset=… (или page), возвращайте total и кусок массива.
Включите токен, ограничьте по IP, добавьте reCAPTCHA (если запросы идут из браузера), используйте HTTPS.

Мини-чеклист продакшена

Приложение слушает process.env.PORT.
Включён HTTPS.
Настроен rate-limit.
(Опционально) Включён API_TOKEN и CORS под нужные домены.
Делаете бэкап data.json.

Реквизиты: Украина, 61202, Харьков, пр. Людвига Свободы 26/298.
ФО-П Харитинов Олег Сергеевич
IBAN: UA073052990000026001005905889
МФО 305299
ИНН 2961615658
ПАТ КБ "ПриватБанк"
mail:
Документы:
Служба поддержки: телефон + 380 57 7209279
создать тикет

Выберите язык