🔥 0
0
Lesson 8 of 10 18 min +100 XP

Building an HTTP Server

Node.js was built for networking. Let's create an HTTP server from scratch - no frameworks needed.

Your First Server

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello, World!');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});
Run it:
node server.js
Output:
Server running at http://localhost:3000/

Open http://localhost:3000 in your browser - you'll see "Hello, World!"

Press Ctrl+C to stop the server.

Understanding Request and Response

const http = require('http');

const server = http.createServer((req, res) => {
  // req = incoming request
  console.log('Method:', req.method);    // GET, POST, etc.
  console.log('URL:', req.url);          // /path/to/resource
  console.log('Headers:', req.headers);  // Object with all headers

  // res = our response
  res.statusCode = 200;                  // HTTP status
  res.setHeader('Content-Type', 'text/plain');
  res.write('Hello ');                   // Write partial response
  res.end('World!');                     // Complete the response
});

server.listen(3000);

Basic Routing

Route different URLs to different responses:

// router.js
const http = require('http');

const server = http.createServer((req, res) => {
  // Set default headers
  res.setHeader('Content-Type', 'text/plain');

  // Route based on URL
  if (req.url === '/') {
    res.statusCode = 200;
    res.end('Welcome to the homepage!');
  } else if (req.url === '/about') {
    res.statusCode = 200;
    res.end('About us page');
  } else if (req.url === '/contact') {
    res.statusCode = 200;
    res.end('Contact us at hello@example.com');
  } else {
    res.statusCode = 404;
    res.end('Page not found');
  }
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});
Test it:
curl http://localhost:3000/
curl http://localhost:3000/about
curl http://localhost:3000/unknown
Output:
Welcome to the homepage!
About us page
Page not found

Returning JSON

APIs return JSON. Here's how:

// api.js
const http = require('http');

const users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com' }
];

const server = http.createServer((req, res) => {
  // CORS headers (allow browser requests from any origin)
  res.setHeader('Access-Control-Allow-Origin', '*');

  if (req.url === '/api/users' && req.method === 'GET') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(users));
  } else if (req.url.startsWith('/api/users/') && req.method === 'GET') {
    const id = parseInt(req.url.split('/')[3]);
    const user = users.find(u => u.id === id);

    if (user) {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify(user));
    } else {
      res.writeHead(404, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'User not found' }));
    }
  } else {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

server.listen(3000, () => {
  console.log('API running at http://localhost:3000/');
});
Test it:
curl http://localhost:3000/api/users
curl http://localhost:3000/api/users/1
curl http://localhost:3000/api/users/99
Output:
[{"id":1,"name":"Alice","email":"alice@example.com"},...]
{"id":1,"name":"Alice","email":"alice@example.com"}
{"error":"User not found"}

Handling POST Requests

POST requests have a body. You need to collect it:

// post-server.js
const http = require('http');

const users = [];

const server = http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json');

  if (req.url === '/api/users' && req.method === 'GET') {
    res.end(JSON.stringify(users));
  } else if (req.url === '/api/users' && req.method === 'POST') {
    // Collect the request body
    let body = '';

    req.on('data', chunk => {
      body += chunk.toString();
    });

    req.on('end', () => {
      try {
        const newUser = JSON.parse(body);
        newUser.id = users.length + 1;
        users.push(newUser);

        res.statusCode = 201;
        res.end(JSON.stringify(newUser));
      } catch (err) {
        res.statusCode = 400;
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });
  } else {
    res.statusCode = 404;
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});
Test it:
# Create a user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'

# Get all users
curl http://localhost:3000/api/users
Output:
{"name":"Alice","email":"alice@example.com","id":1}
[{"name":"Alice","email":"alice@example.com","id":1}]

Serving HTML Files

// html-server.js
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer((req, res) => {
  // Default to index.html
  let filePath = req.url === '/' ? '/index.html' : req.url;
  filePath = path.join(__dirname, 'public', filePath);

  // Get file extension for content type
  const ext = path.extname(filePath);
  const contentTypes = {
    '.html': 'text/html',
    '.css': 'text/css',
    '.js': 'text/javascript',
    '.json': 'application/json',
    '.png': 'image/png',
    '.jpg': 'image/jpeg'
  };

  const contentType = contentTypes[ext] || 'text/plain';

  // Read and serve the file
  fs.readFile(filePath, (err, content) => {
    if (err) {
      if (err.code === 'ENOENT') {
        res.writeHead(404, { 'Content-Type': 'text/html' });
        res.end('<h1>404 - File Not Found</h1>');
      } else {
        res.writeHead(500);
        res.end('Server Error');
      }
    } else {
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content);
    }
  });
});

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

Create public/index.html:

<!DOCTYPE html>
<html>
<head>
  <title>My Node.js Server</title>
</head>
<body>
  <h1>Hello from Node.js!</h1>
  <p>This HTML file is served by our custom server.</p>
</body>
</html>
Crio.Do

Deploy Your Server

Get a dedicated cloud environment to build and deploy Node.js servers. No local setup needed.

Start Free Trial

Try It Yourself

Build a simple todo API:

// todo-api.js
const http = require('http');

let todos = [
  { id: 1, text: 'Learn Node.js', done: false },
  { id: 2, text: 'Build an API', done: false }
];

function sendJSON(res, statusCode, data) {
  res.writeHead(statusCode, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify(data));
}

function parseBody(req) {
  return new Promise((resolve, reject) => {
    let body = '';
    req.on('data', chunk => body += chunk);
    req.on('end', () => {
      try {
        resolve(body ? JSON.parse(body) : {});
      } catch (e) {
        reject(e);
      }
    });
  });
}

const server = http.createServer(async (req, res) => {
  const { method, url } = req;

  // GET /todos - List all todos
  if (url === '/todos' && method === 'GET') {
    sendJSON(res, 200, todos);
  }
  // POST /todos - Create a todo
  else if (url === '/todos' && method === 'POST') {
    const body = await parseBody(req);
    const todo = {
      id: todos.length + 1,
      text: body.text,
      done: false
    };
    todos.push(todo);
    sendJSON(res, 201, todo);
  }
  // PATCH /todos/:id - Toggle done status
  else if (url.match(/^\/todos\/\d+$/) && method === 'PATCH') {
    const id = parseInt(url.split('/')[2]);
    const todo = todos.find(t => t.id === id);
    if (todo) {
      todo.done = !todo.done;
      sendJSON(res, 200, todo);
    } else {
      sendJSON(res, 404, { error: 'Todo not found' });
    }
  }
  // DELETE /todos/:id - Delete a todo
  else if (url.match(/^\/todos\/\d+$/) && method === 'DELETE') {
    const id = parseInt(url.split('/')[2]);
    const index = todos.findIndex(t => t.id === id);
    if (index !== -1) {
      todos.splice(index, 1);
      sendJSON(res, 200, { message: 'Deleted' });
    } else {
      sendJSON(res, 404, { error: 'Todo not found' });
    }
  }
  // 404 for everything else
  else {
    sendJSON(res, 404, { error: 'Not found' });
  }
});

server.listen(3000, () => {
  console.log('Todo API at http://localhost:3000/');
  console.log('Try: curl http://localhost:3000/todos');
});
Test it:
# List todos
curl http://localhost:3000/todos

# Create a todo
curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"text":"Read documentation"}'

# Toggle done
curl -X PATCH http://localhost:3000/todos/1

# Delete
curl -X DELETE http://localhost:3000/todos/2

Key Takeaways

  • http.createServer creates a server with a request handler
  • req contains method, url, headers, and body (as stream)
  • res is used to send status, headers, and body back
  • JSON.stringify converts objects to JSON strings
  • Routing is matching req.url and req.method to handlers
  • POST bodies must be collected from the stream

This manual routing works, but gets tedious. Next, let's see how Express makes this much easier!

🧠 Quick Quiz

Test your understanding of this lesson.

1

What does req.url contain?

2

What does res.end() do?

3

How do you return JSON from an HTTP server?

Async/Await