Skip to main content

CRUD Operations

CRUD (Create, Read, Update, Delete) operations are fundamental in web development. Here's how to create a minimal HTTP server with all HTTP verbs using the most popular libraries in each language.

Minimal HTTP Server with CRUD​

// Express.js
const express = require('express');
const app = express();
app.use(express.json());

// CREATE - POST
app.post('/items', (req, res) => {
res.status(201).json({ message: 'Item created successfully' });
});

// READ - GET all
app.get('/items', (req, res) => {
res.json({ message: 'Get all items' });
});

// READ - GET one
app.get('/items/:id', (req, res) => {
res.json({ message: `Get item with id: ${req.params.id}` });
});

// UPDATE - PUT
app.put('/items/:id', (req, res) => {
res.json({ message: `Update item with id: ${req.params.id}` });
});

// DELETE
app.delete('/items/:id', (req, res) => {
res.status(204).json({ message: `Delete item with id: ${req.params.id}` });
});

app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});

Key Takeaways​

  • Framework maturity -- JavaScript (Express), Python (Flask), Go (Gin), Java (Spring Boot), and Ruby (Sinatra) have mature, convention-rich ecosystems with built-in middleware, validation, and ORM integrations. For example, Express offers express.json() and app.post('/items', handler) out of the box, while Spring Boot auto-configures embedded Tomcat and dependency injection. In contrast, C with libmicrohttpd requires manual strcmp-based routing, manual JSON parsing, and custom request/response handling. Choose mature frameworks when shipping APIs quickly; consider C only for embedded systems or minimal binaries.

  • Routing patterns -- Declarative routing is the norm: Express uses app.get('/items/:id', ...), Gin uses r.GET("/items/:id", getItem), Flask uses @app.route('/items/<int:item_id>', methods=['GET']), and Sinatra uses get '/items/:id' do. Spring Boot uses annotations like @GetMapping("/{id}") with @PathVariable. These patterns map verbs and paths to handlers cleanly. C requires a single callback that dispatches via strcmp(method, "GET") and strcmp(url, "/items"), which scales poorly. Prefer declarative routing for maintainability and consistency across your API surface.

  • Minimal vs full-stack -- ASP.NET Core Minimal API and Zig (httpz) emphasize concise route definitions: C# uses app.MapGet("/items/{id}", (int id) => Results.Ok(...)) with no controllers; Zig uses try router.get("/items/:id", getItem, .{}). Spring Boot and Vapor offer batteries-included structures: Spring provides @RestController, @RequestMapping, auto-serialization, and dependency injection; Vapor integrates with Swift's type system and async/await. Minimal APIs suit microservices and prototypes; full-stack frameworks suit large applications with shared conventions and tooling.

  • Request/response ergonomics -- Most frameworks offer convenient JSON handling: Express with res.json({ message: '...' }), Gin with c.JSON(http.StatusOK, gin.H{...}), Flask with jsonify({'message': '...'}), and C# with Results.Ok(new { message = "..." }). Zig's httpz requires explicit res.json(.{ .message = "..." }, .{}) and manual arena allocation for formatted strings. Choose frameworks with ergonomic serialization when building REST APIs; Zig's explicit approach prioritizes control and predictability over convenience.

  • Parameter extraction -- Languages differ in how path parameters reach handlers: Python (Flask) uses def get_item(item_id) with type conversion; Go (Gin) uses c.Param("id"); Rust (Actix) uses web::Path<u32> for typed extraction; Java (Spring) uses @PathVariable int id. C and Zig require manual parsing: req.param("id").? in Zig or extracting from URL strings in C. Typed extraction reduces bugs and boilerplate. Prefer frameworks that parse parameters into typed values automatically.