Init
This commit is contained in:
13
index.js
Normal file
13
index.js
Normal file
@ -0,0 +1,13 @@
|
||||
import server from "./src/server.js";
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const HOST = process.env.HOST || "127.0.0.1";
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`Todo API running at http://${HOST}:${PORT}/`);
|
||||
console.log("Available endpoints:");
|
||||
console.log(" GET /todos - Get all todos");
|
||||
console.log(" POST /todos - Create todo");
|
||||
console.log(" PUT /todos/:id - Update todo");
|
||||
console.log(" DELETE /todos/:id - Delete todo");
|
||||
});
|
11
package.json
Normal file
11
package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "nodeapi-template",
|
||||
"version": "1.0.0",
|
||||
"description": "Basic node.js API template",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "node --watch index.js"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
111
src/controllers/todoController.js
Normal file
111
src/controllers/todoController.js
Normal file
@ -0,0 +1,111 @@
|
||||
import { sendJSON, parseBody } from "../utils/helpers.js";
|
||||
import {
|
||||
getTodos,
|
||||
addTodo,
|
||||
updateTodoById,
|
||||
deleteTodoById,
|
||||
} from "../data/todos.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Todo
|
||||
* @property {number} id - Todo unique identifier
|
||||
* @property {string} text - Todo text content
|
||||
* @property {boolean} completed - Todo completion status
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} CreateTodoRequest
|
||||
* @property {string} text - Todo text content
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} UpdateTodoRequest
|
||||
* @property {string} [text] - Todo text content
|
||||
* @property {boolean} [completed] - Todo completion status
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RequestWithParams
|
||||
* @property {Object} params - Request parameters
|
||||
* @property {string} params.id - Todo ID from URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get all todos
|
||||
* @param {import('http').IncomingMessage} req - HTTP request object
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @returns {void}
|
||||
*/
|
||||
export const getAllTodos = (req, res) => {
|
||||
/** @type {Todo[]} */
|
||||
const todos = getTodos();
|
||||
sendJSON(res, 200, todos);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new todo
|
||||
* @param {import('http').IncomingMessage} req - HTTP request object
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @returns {void}
|
||||
*/
|
||||
export const createTodo = (req, res) => {
|
||||
parseBody(req, (error, data) => {
|
||||
if (error) {
|
||||
return sendJSON(res, 400, { error: "Invalid JSON" });
|
||||
}
|
||||
|
||||
/** @type {CreateTodoRequest} */
|
||||
const todoData = data;
|
||||
|
||||
if (!todoData.text) {
|
||||
return sendJSON(res, 400, { error: "Text is required" });
|
||||
}
|
||||
|
||||
/** @type {Todo} */
|
||||
const newTodo = addTodo(todoData.text);
|
||||
sendJSON(res, 201, newTodo);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update an existing todo
|
||||
* @param {import('http').IncomingMessage & RequestWithParams} req - HTTP request object with params
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @returns {void}
|
||||
*/
|
||||
export const updateTodo = (req, res) => {
|
||||
parseBody(req, (error, data) => {
|
||||
if (error) {
|
||||
return sendJSON(res, 400, { error: "Invalid JSON" });
|
||||
}
|
||||
|
||||
/** @type {UpdateTodoRequest} */
|
||||
const updateData = data;
|
||||
|
||||
/** @type {Todo | null} */
|
||||
const updatedTodo = updateTodoById(req.params.id, updateData);
|
||||
|
||||
if (updatedTodo) {
|
||||
sendJSON(res, 200, updatedTodo);
|
||||
} else {
|
||||
sendJSON(res, 404, { error: "Todo not found" });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a todo
|
||||
* @param {import('http').IncomingMessage & RequestWithParams} req - HTTP request object with params
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @returns {void}
|
||||
*/
|
||||
export const deleteTodo = (req, res) => {
|
||||
/** @type {boolean} */
|
||||
const deleted = deleteTodoById(req.params.id);
|
||||
|
||||
if (deleted) {
|
||||
sendJSON(res, 200, { message: "Todo deleted" });
|
||||
} else {
|
||||
sendJSON(res, 404, { error: "Todo not found" });
|
||||
}
|
||||
};
|
72
src/data/todos.js
Normal file
72
src/data/todos.js
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @typedef {Object} Todo
|
||||
* @property {number} id - Todo unique identifier
|
||||
* @property {string} text - Todo text content
|
||||
* @property {boolean} completed - Todo completion status
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} TodoUpdate
|
||||
* @property {string} [text] - Todo text content
|
||||
* @property {boolean} [completed] - Todo completion status
|
||||
*/
|
||||
|
||||
/**
|
||||
* In-memory storage for todos
|
||||
* @type {Todo[]}
|
||||
*/
|
||||
let todos = [
|
||||
{ id: 1, text: "Learn Node.js", completed: false },
|
||||
{ id: 2, text: "Build an API", completed: true },
|
||||
];
|
||||
|
||||
/**
|
||||
* Get all todos
|
||||
* @returns {Todo[]} Array of all todos
|
||||
*/
|
||||
export const getTodos = () => todos;
|
||||
|
||||
/**
|
||||
* Add a new todo
|
||||
* @param {string} text - Todo text content
|
||||
* @returns {Todo} The newly created todo
|
||||
*/
|
||||
export const addTodo = (text) => {
|
||||
/** @type {Todo} */
|
||||
const newTodo = {
|
||||
id: todos.length > 0 ? Math.max(...todos.map((t) => t.id)) + 1 : 1,
|
||||
text,
|
||||
completed: false,
|
||||
};
|
||||
todos.push(newTodo);
|
||||
return newTodo;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a todo by ID
|
||||
* @param {string} id - Todo ID to update
|
||||
* @param {TodoUpdate} updates - Properties to update
|
||||
* @returns {Todo | null} Updated todo or null if not found
|
||||
*/
|
||||
export const updateTodoById = (id, updates) => {
|
||||
/** @type {number} */
|
||||
const todoIndex = todos.findIndex((todo) => todo.id === parseInt(id));
|
||||
if (todoIndex === -1) return null;
|
||||
|
||||
todos[todoIndex] = { ...todos[todoIndex], ...updates };
|
||||
return todos[todoIndex];
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a todo by ID
|
||||
* @param {string} id - Todo ID to delete
|
||||
* @returns {boolean} True if deleted, false if not found
|
||||
*/
|
||||
export const deleteTodoById = (id) => {
|
||||
/** @type {number} */
|
||||
const todoIndex = todos.findIndex((todo) => todo.id === parseInt(id));
|
||||
if (todoIndex === -1) return false;
|
||||
|
||||
todos.splice(todoIndex, 1);
|
||||
return true;
|
||||
};
|
83
src/server.js
Normal file
83
src/server.js
Normal file
@ -0,0 +1,83 @@
|
||||
import http from "node:http";
|
||||
import url from "node:url";
|
||||
import { sendJSON, setCorsHeaders } from "./utils/helpers.js";
|
||||
import {
|
||||
getAllTodos,
|
||||
createTodo,
|
||||
updateTodo,
|
||||
deleteTodo,
|
||||
} from "./controllers/todoController.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} ApiEndpoints
|
||||
* @property {string} "GET /todos" - Get all todos
|
||||
* @property {string} "POST /todos" - Create todo
|
||||
* @property {string} "PUT /todos/:id" - Update todo
|
||||
* @property {string} "DELETE /todos/:id" - Delete todo
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} RootResponse
|
||||
* @property {string} message - API name
|
||||
* @property {string} version - API version
|
||||
* @property {ApiEndpoints} endpoints - Available endpoints
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ErrorResponse
|
||||
* @property {string} error - Error message
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP server that handles Todo API requests
|
||||
* @type {http.Server}
|
||||
*/
|
||||
const server = http.createServer((req, res) => {
|
||||
/** @type {url.UrlWithParsedQuery} */
|
||||
const parsedUrl = url.parse(req.url, true);
|
||||
/** @type {string} */
|
||||
const path = parsedUrl.pathname;
|
||||
/** @type {string} */
|
||||
const method = req.method;
|
||||
|
||||
setCorsHeaders(res);
|
||||
|
||||
if (method === "OPTIONS") {
|
||||
res.statusCode = 200;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Routes
|
||||
if (path === "/" && method === "GET") {
|
||||
// Root route
|
||||
sendJSON(res, 200, {
|
||||
message: "Todo API",
|
||||
version: "1.0.0",
|
||||
endpoints: {
|
||||
"GET /todos": "Get all todos",
|
||||
"POST /todos": "Create todo",
|
||||
"PUT /todos/:id": "Update todo",
|
||||
"DELETE /todos/:id": "Delete todo",
|
||||
},
|
||||
});
|
||||
} else if (path === "/todos" && method === "GET") {
|
||||
getAllTodos(req, res);
|
||||
} else if (path === "/todos" && method === "POST") {
|
||||
createTodo(req, res);
|
||||
} else if (path.match(/^\/todos\/\d+$/) && method === "PUT") {
|
||||
/** @type {string} */
|
||||
const id = path.split("/")[2];
|
||||
req.params = { id };
|
||||
updateTodo(req, res);
|
||||
} else if (path.match(/^\/todos\/\d+$/) && method === "DELETE") {
|
||||
/** @type {string} */
|
||||
const id = path.split("/")[2];
|
||||
req.params = { id };
|
||||
deleteTodo(req, res);
|
||||
} else {
|
||||
sendJSON(res, 404, { error: "Route not found" });
|
||||
}
|
||||
});
|
||||
|
||||
export default server;
|
53
src/utils/helpers.js
Normal file
53
src/utils/helpers.js
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* @callback ParseBodyCallback
|
||||
* @param {Error | null} error - Parse error if any
|
||||
* @param {Object} data - Parsed JSON data
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set CORS headers on response
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @returns {void}
|
||||
*/
|
||||
export const setCorsHeaders = (res) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
};
|
||||
|
||||
/**
|
||||
* Send JSON response
|
||||
* @param {import('http').ServerResponse} res - HTTP response object
|
||||
* @param {number} statusCode - HTTP status code
|
||||
* @param {Object | Array} data - Data to send as JSON
|
||||
* @returns {void}
|
||||
*/
|
||||
export const sendJSON = (res, statusCode, data) => {
|
||||
res.statusCode = statusCode;
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse request body as JSON
|
||||
* @param {import('http').IncomingMessage} req - HTTP request object
|
||||
* @param {ParseBodyCallback} callback - Callback function with parsed data or error
|
||||
* @returns {void}
|
||||
*/
|
||||
export const parseBody = (req, callback) => {
|
||||
/** @type {string} */
|
||||
let body = "";
|
||||
req.on("data", (chunk) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on("end", () => {
|
||||
try {
|
||||
/** @type {Object} */
|
||||
const data = body ? JSON.parse(body) : {};
|
||||
callback(null, data);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user