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

Pacing!

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

  debugger
}

myFunction()

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>
</form>

And modern frontend frameworks are essentially this:

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

  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":

my_field=wow&a_few_pi_digits=3.14159

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'

exports.data = 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>

<p>Message:</p>

<p>{{message}}</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 (route.data) routeContext = await route.data(request)

  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">
  <div>
    <input type="email" name="email" id="email" placeholder="your email"
  </div>
  <div>
    <textarea name="message" id="message" placeholder="type your message to yourself" rows="10" cols="50"></textarea>
  </div>

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

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: https://t.me/frameworkless

Twitter: @mtimofiiv

Made with Slides.com