#100DaysOfMERN - Day 21

Subscribe to my newsletter and never miss my upcoming articles

✏ Server with Express vs. Vanilla JS

I've used Express.js to set up a server a couple of times in previous posts, and it was astonishingly easy. As a reminder, all I need is to install Express and three lines of code:


npm i express


import express from 'express';

const app = express();

app.listen(3001, console.log('express is serving'))

To get a better understanding of what's happening under the hood, today I'm going to set up a second server with only Vanilla JS, and compare the two.


No need to install anything, I only need the http object, which is already in node's core:


import http from 'http';

const app = http.createServer((req, res) => {});

app.listen(3002, console.log('vanilla is serving'))

(Note: if you prefer import statements over require, you'll have to add "type":"module" to the package.json)

Starting both servers now looks the same in the node console, they're both logging that they're serving. The main difference at this point: Opening the page in a browser. While the Express server responds with a 404 (Not Found) error right away, the Vanilla server is stuck with an endless spinner.

✏ Sending a Response

With Express, I've used the .get method to send a response in the past:

import express from 'express';
const app = express();

app.get('/', (req, res) => {
    res.send('<h1>Express is Serving</h1>');

app.listen(3001, console.log('express is serving'))

Similar with Vanilla:

import http from 'http';

const app = http.createServer((req, res) => {
    res.write('<h1>Vanilla is Serving</h1>');

app.listen(3002, console.log('vanilla is serving'))

A few differences here:

  • the Express server only responds to the / route
  • the Vanilla server sends its response regardless of the route
  • if I don't add res.end(), I still get my content but I'm stuck with an endless spinner again

res.end() takes a parameter though, so I could also use it like this:

res.end('<h1>Vanilla is Serving</h1>')

and get rid of the .write method.

✏ Response Headers

Checking the Network tab in the console, it looks like Express automatically adds a Content-Type (text/html) and a Content-Length to the Response Header. With Vanilla, I'd have to do that manually:

import http from 'http';

const app = http.createServer((req, res) => {
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.end('<h1>Vanilla is Serving</h1>')

app.listen(3002, console.log('vanilla is serving'))

res.writeHead needs a number (status code) as parameter, and it optionally takes a string for a status message, and an object for the Headers. Using all three of those:

const app = http.createServer((req, res) => {
    const body = '<h1>Vanilla is Serving</h1>';

    res.writeHead(200, 'HI!', {
        'Content-Length': Buffer.byteLength(body),
        'Content-Type': 'text/html'


✏ Setting up Routes

With Express, this is already done (in fact, I couldn't find a way not to set up a route, and to send a default response for every possible GET request).

The Vanilla server will need a little more configuration. First, read the requested URI from the request object, then handle each request with an if/else chain or a switch:

const app = http.createServer((req, res) => {

    let body;

        case '/': body = '<h1>Vanilla is Serving</h1>'; break;
        case '/hello': body = '<h1>world</h1>'; break;
        default: body = '<h1>check your spelling</h1>';

    res.writeHead(200, 'HI!', {
        'Content-Length': Buffer.byteLength(body),
        'Content-Type': 'text/html'


So much for the basic server skeleton. So far, the Express server doesn't look so different compared to Vanilla JS, but I suppose the advantages will become obvious as soon as more functionality is added - which I'll do tomorrow.

✏ Resources

Some helpful articles:

MDN - Node Server without Framework

Medium - Node server without Express

Medium - Simple Node JS server without Express JS

✏ Recap

I've learned

  • how to set up a server with Express vs. Vanilla
  • how to set up routes
  • how to serve hardcoded content

✏ Next:

  • serving static files with Express vs. Vanilla

✏ Thanks for reading!

I do my best to thoroughly research the things I learn, but if you find any errors or have additions, please leave a comment below, or @ me on Twitter. If you liked this post, I invite you to subsribe to my newsletter. Until next time 👋

✏ Previous Posts

  • Day 1: Introduction, Node.js, Node.js in the terminal
  • Day 2: npm, node_modules, package.json and package-lock.json, local vs global installation of packages
  • Day 3: Create a React app without create-react-app, Webpack, Babel
  • Day 4: npx and cowsay
  • Day 5: npm vs. npx, npm audit, semantic versioning and update rules
  • Day 6: Call stack, event loop, JavaScript engine, JavaScript runtime
  • Day 7: Call stack and event loop in Node.js, setImmediate()
  • Day 8: setImmediate(), process.nextTick(), event loop phases in Node.js
  • Day 9: Network requests with XMLHttpRequest and callbacks
  • Day 10: Promises
  • Day 11: Network requests with XMLHttpRequest and Promises
  • Day 12: React Quiz App part 1
  • Day 13: React Hangman
  • Day 14: FullStackOpen course 1: details of GET and POST requests, request headers
  • Day 15: React Hangman: Trigger fetch with click event callback vs useEffect
  • Day 16: REST API and CRUD
  • Day 17: Boring Book App part 1: React Frontend, Express Backend, GET requests, CORS
  • Day 18: Boring Book App part 2: POST request, File System API
  • Day 19: Boring Book App part 3: Request Parameters, DELETE request
  • Day 20: Boring Book App part 4: PUT request

No Comments Yet