EN
UAH

Simple REST API in Node.js: GET/POST/DELETE

What this example does

Blog API Endpoints:

  • GET /api/items — list of notes/tasks
  • POST /api/items — creates a new note/task
  • DELETE /api/items/:id — deletes a note/task by its ID

Storage: data.json file (suitable for shared hosting).
Security: rate limiting, simple token check (optional).
Service routes: /health (status check), / (quick help).

Project Structure

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.

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 — simple file-based storage


// 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 — server and API routes.


// 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}`);
});
  
  

.env (for local development)


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.

Node.js
Fast & Reliable Node.js Hosting
Start Your Project with Just a Few Clicks!
Free SSL CertificatPowered by Modern Servers7-Day Free Trial
View Plans

Running Locally


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"
  
  

Deploying to ISPmanager on Shared Hosting

  • Add a domain or website in ISPmanager
  • Upload the project files to your website directory
  • Run in terminal/SSH: npm install
  • In the Node.js section, specify:
    • Select the Node.js version (LTS)
    • Startup: app.js or npm start
    • Environment variables (optional): NODE_ENV=production, API_TOKEN=...
    • Link your app to a domain or subdomain (the panel sets up the proxy automatically).
  • Enable HTTPS (Let’s Encrypt) on your domain.
  • Check: https://your-domain/api/items.

cURL request examples

Get list

curl -s https://your-domain/api/items

Create entry

  
curl -s -X POST https://your-domain/api/items \
  -H "Content-Type: application/json" \
  -d '{"title":"Buy hosting","done":false}'
    
  

Delete entry


  curl -s -X DELETE https://your-domain/api/items/ID_FROM_LIST

If the token is enabled, add: -H "Authorization: Bearer my-secret-token".

FAQ (Frequently Asked Questions)

For mini-APIs, demos, and MVPs — yes. For serious projects, switch to a database (MySQL/PostgreSQL) or use a VPS.
Add a route PATCH /api/items/:id and update title/done using the same logic as in POST.
Read the parameters ?limit=…&offset=… (or page), return total and a slice of the array.
Enable a token, restrict access by IP (allowlist), add reCAPTCHA if requests come from the browser, and enforce HTTPS.

Production mini-checklist

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.

Contact details: Ukraine, 61202, Kharkiv, Ludviga Svobody st. 26-298.
FO-P Kharitinov Oleg Sergeevich
IBAN UA073052990000026001005905889
tax.number 2961615658
PrivatBank
mail:
Documentation:
Support service: телефон + 380 57 7209279
Create a ticket