✏ 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:
server-express
npm i express
express.js
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.
server-vanilla
No need to install anything, I only need the http
object, which is already in node's core:
vanilla.js
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>');
res.end()
});
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'
});
res.end(body)
});
✏ 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;
switch(req.url){
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'
});
res.end(body)
});
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
andpackage-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