Skip to content

Instantly share code, notes, and snippets.

@clavery
Last active July 7, 2023 02:44
Show Gist options
  • Save clavery/54c2d664ec3b504959ebd1b13f741129 to your computer and use it in GitHub Desktop.
Save clavery/54c2d664ec3b504959ebd1b13f741129 to your computer and use it in GitHub Desktop.
Simple HTTP Rest Server in Node that operates on a JSON file backend
/*
* Simple API Sample Server
*
* Serves from file ./_data.json writing to arrays with GET, POST, PUT, PATCH, DELETE
*
* Create a _data.json file like:
*
* {
* "todos": [],
* "people": []
* }
*
* Then use Rest actions against /todos/{id}, /people/{id}
*/
const http = require('node:http');
const fs = require('node:fs');
const crypto = require("node:crypto");
const DATA_FILE = process.env.DATA_FILE ?? "./_data.json"
/**
* create UUID for IDs
* @returns {string} UUID
*/
function uuid4() {
var rnd = crypto.randomBytes(16);
rnd[6] = (rnd[6] & 0x0f) | 0x40;
rnd[8] = (rnd[8] & 0x3f) | 0x80;
rnd = rnd.toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/);
rnd.shift();
return rnd.join('-');
}
const server = http.createServer(
/**
* @param {http.ClientRequest} req
* @param {http.ServerResponse} res
*/
(req, res) => {
console.info(`[REQ] ${req.method} ${req.url}`)
res.setHeader("Access-Control-Allow-Origin", "*")
var body = []
req.on('data', (chunk) => {
body.push(chunk)
}).on('end', () => {
body = Buffer.concat(body).toString()
var resp = {}
const contentType = "application/json"
res.setHeader("content-type", contentType)
var data;
try {
data = JSON.parse(fs.readFileSync(DATA_FILE).toString())
} catch (e) {
console.error(`[RES] 500 ${e.message}`)
res.statusCode = 500
res.write(JSON.stringify({"error": e.message}, null, 2))
res.end();
return
}
if (req.method === 'OPTIONS') {
res.setHeader("Access-Control-Allow-Methods", "POST, PUT, PATCH, GET, OPTIONS, DELETE")
res.setHeader("Access-Control-Allow-Headers", "*")
res.statusCode = 200
res.end()
return
}
try {
if (req.url.length === 1) {
resp = data
} else {
var parts = req.url.split('/')
var type = parts[1]
let id = parts[2]
if (!data[type]) {
res.statusCode = 404
resp = {"error": "Not Found"}
} else if (req.method === "GET") {
if (id) {
resp = data[type].find(i => i.id === id)
if (!resp) {
res.statusCode = 404
resp = {"error": "Not Found"}
}
} else {
resp = data[type]
}
} else if (req.method === "POST") {
if (id) {
res.statusCode = 405
resp = {"error": "Method not Allowed"}
} else {
id = uuid4()
resp = {
id: id,
...JSON.parse(body)
}
data[type].push(resp)
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2))
}
// TODO: put shouldn't act exactly like patch
} else if (req.method === "PUT" || req.method === "PATCH") {
let obj = data[type].find(i => i.id === id)
if (!obj) {
res.statusCode = 404
resp = {"error": "Not Found"}
} else {
resp = Object.assign(obj, JSON.parse(body), {id})
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2))
}
} else if (req.method === "DELETE") {
let idx = data[type].findIndex(i => i.id === id)
if (idx === -1) {
res.statusCode = 404
resp = {"error": "Not Found"}
} else {
res.statusCode = 204
data[type].splice(idx, 1)
fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2))
}
} else {
throw new Error('unsupported')
}
}
} catch (e) {
res.statusCode = 400
resp = {"error": e.message}
}
const _resp = JSON.stringify(resp, null, 2)
console.info(`[RES] ${res.statusCode} ${contentType}\n${_resp}`)
res.write(_resp)
res.end()
})
});
server.on('clientError', (err, socket) => {
console.error(`[RES] 400 ${err.message}`)
});
server.listen(8000, () => {
console.info("Listening on 8000...")
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment