Continuing Frameworkless.js

Part 3: Forms + a bit of random stuff

Recap of Last Time

  • We built a Node.js app utilising the built-in http.createServer
  • Starts an HTTP server and serves static files out of the ./public directory
  • We implemented dynamic routing with a handy easy to define syntax
  • Our routes use Handlebars template files to display content that's not static


Wawaweewaa we covered a lot last time! This time, we'll go a bit slower.

Comment from last stream:

Let's talk about the debugger

const myFunction = () => {
  const thisIsTruth = true



This code works great in-browser! Breakpoints are amazeballs!


BUT, getting them to work in Node.js requires one more step:

$ node --inspect test.js

Breakpoints to the rescue!

const sampleData = [
    user_id: 1,
    friends: [
      { user_id: 5 },
      { user_id: 6 }
> sampleData
[ { user_id: 1, friends: [ [Object], [Object] ] } ]

Not a lot of visibility into nested objects!

And now, on to forms...

HTML forms are simple:

<form action="/my_endpoint" method="POST" id="my_form" name="my_form">
  <input type="text" name="my_random_text" id="my_random_text">
  <button type="sumbit">Submit!</button>

And modern frontend frameworks are essentially this:

document.getElementById('my_form').addEventListener('submit', event => {

  fetch('/my_endpoint', { method: 'POST', body: { /* form field values */ } })

Let's parse it!

Form data can be complicated to deal with...

  • Content types
  • Multipart data
  • File uploads
  • Validation

an ultra-naive form body parser

// lib/body-parser.js
const { parse: parseFormadata } = require('querystring')

const getRequestBody = request => new Promise((resolve, reject) => {
  let formData = ''

  request.on('data', buffer => formData += buffer.toString())
  request.on('error', reject)

  request.on('end', () => {
    const parsedData = parseFormadata(formData)
    return resolve(parsedData)

module.exports = getRequestBody

NOTE: You probably don't want to use something like this live! It assumes the content is text, that it's not multipart and that the format is a "querystring":


But let's add a route into our app!

// routes/new-reminder.js

const { getRequestBody } = require('../lib/formdata')

exports.uri = '/new'
exports.template = 'new_reminder'
exports.method = 'POST' = async request => {
  const formData = await getRequestBody(request)
  return formData

Let's create a new route:

And a template:

{{!-- templates/new_reminder.hbs --}}

<h1>Data received!</h1>

<p>Email: {{email}}</p>



Time to create a "hook"

// lib/responder.js

const serveRoute = async ({ request, context }, response) => {
  const key = `${request.method}:${request.url}`
  const route = routes[key]

  if (!route) throw new Error('not_found')

  Handlebars.registerPartial('content', route.body)
  const hbs = Handlebars.compile(basePage)

  let routeContext = {}
  if ( routeContext = await

  return response.end(hbs({ ...context, ...routeContext }))

A request to your server has a lifecycle! So let's add a step:

Finally, let's add a form to test!

{{!-- templates/root.hbs --}}

<form action="/new" method="POST">
    <input type="email" name="email" id="email" placeholder="your email"
    <textarea name="message" id="message" placeholder="type your message to yourself" rows="10" cols="50"></textarea>

    <button type="submit">Submit</button>

Awesome! 🤩

Form data is hard. We may have built a really simple parser but it works and all we need at the moment!

Well done, you made it! 👏👏👏

See you next time where we look at Hooking up a database to save some of that beautiful data.


Please don't hesitate to contact me or leave feedback on the course:

Telegram channel:

Twitter: @mtimofiiv