We offer you a grace period for 3 days to use VDS to check your projects.
API Endpoints:
Storage: data.json file (suitable for shared hosting). Security: rate limiting, simple token check (optional). Service routes: /health (status check), / (quick help).
rest-api/ +-- package.json +-- app.js +-- storage.js # working with the data.json file +-- data.json # created on first start automatically +-- .env # optional for local development.
{ "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 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 { // overwrite with an empty array if the file is corrupted 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 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'; // SECURITY, LOGGING, PARSING app.use(helmet()); app.use(morgan(isProd ? 'combined' : 'dev')); app.use(express.json()); // API accepts JSON // basic CORS setup (for browser requests) 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 (anti-spam) const apiLimiter = rateLimit({ windowMs: 60 * 1000, max: 60, // max 60 requests per minute per IP standardHeaders: true, legacyHeaders: false }); app.use('/api/', apiLimiter); // OPTIONAL TOKEN (set API_TOKEN in environment) // Example: Authorization: Bearer my-secret-token function authIfEnabled(req, res, next) { const token = process.env.API_TOKEN; if (!token) return next(); // skip if token is not configured 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); }); // Create 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); // add to the beginning writeAll(items); res.status(201).json(item); }); // Deletion 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 and 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}`); });
PORT=3000 NODE_ENV=development # Optional — enable token authentication: # API_TOKEN=my-secret-token
In production (ISPmanager), set the environment variables in the control panel. You don’t need to upload the .env file.
npm install npm start # or 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":"First Note","done":false}' curl -X DELETE http://localhost:3000/api/items/ If API_TOKEN is set, include this header: -H "Authorization: Bearer my-secret-token"
Get list
curl -s https://your-domain/api/items
curl -s -X POST https://your-domain/api/items \ -H "Content-Type: application/json" \ -d '{"title":"Buy hosting","done":false}'
curl -s -X DELETE https://your-domain/api/items/ID_FROM_LIST
If the token is enabled, add: -H "Authorization: Bearer my-secret-token".
The application listens on process.env.PORT. HTTPS is enabled. Rate limiting is configured. (Optional) API_TOKEN is enabled and CORS is restricted to the required domains. Backup of data.json is created.