Welcome to Frameworkless.js

(boring) details

Who is the course for? | How long will it take? | Other stuff...

What are we going to Learn?

Email future you, today...

...aka schedule an email to yourself in the future to remind you about something.

  • Build your product idea – fast & leanβ„’
  • Get framework learning out of your critical path
  • End up with a solid starter project you can reference or adapt to any other idea you want

We will build a real product together:

Well then, let's jump right in...

$ mkdir remind.ist && cd remind.ist
$ npm init

Don't have Node.js installed? Get it through Node Version Manager (nvm):
https://github.com/nvm-sh/nvm#installation-and-update

The simplest Node.js app ever...

// app.js
const run = () => {
  console.log('Run, pump those crazy legs!')
}

run()

...and this course is over, thanks for watching!

Joking, obviously πŸ˜‰

$ node app.js
Run, pump those crazy legs!
$ 

Auto-restart on changes

$ npm install nodemon --save-dev
// package.json
{
  ...
  "scripts": {
    "start": "nodemon --inspect -e js,html,hbs,sql,css,scss app.js"
  }
  ...
}

More about Nodemon:

https://nodemon.io

$ npm run start

Environment variables

// app.js
require('dotenv').config()

const { createServer } = require('http')

const PORT = process.env.PORT || 1234

const server = createServer((request, response) => {
  return response.end('Look, this is the response!')
})

server.listen(PORT, () => {
  console.log(`We are running the server on port ${PORT}`)
})
# .env
PORT=1234
$ npm install dotenv --save-dev

Let's do something useful though...

// app.js
const { createServer } = require('http')

const PORT = 1234

const server = createServer((request, response) => {
  return response.end('Look, this is the response!')
})

server.listen(PORT, () => {
  console.log(`We are running the server on port ${PORT}`)
})

Documentation for Node's standard HTTP library:

https://nodejs.org/dist/latest-v12.x/docs/api/http.html

Time to reflect on your work

You did awesome! πŸ‘ŒπŸ˜˜

HTTP server in only a couple lines of code!

We'll call it...responder...I guess

New files...new files everywhere

  1. Move HTTP server to a new file, initialisers/http.js
  2. Create a public folder to serve static files from
  3. Create a lib/responder.js library we will utilise to serve various types of content (but for now just static files)
  4. Create a config/constants.js file to store customisable pieces in a central place

Whitelisting extensions

// config/constants.js
exports.STATIC_EXTENSIONS = [
  'jpg',
  'jpeg',
  'gif',
  'png',
  'svg',
  'ico',
  'css',
  'js',
  'xml',
  'webmanifest',
  'txt',
  'html',
  'eot',
  'ttf',
  'woff',
]

And now we touch the file system

// lib/responder.js
const { open } = require('fs').promises

const { STATIC_EXTENSIONS } = require('../config/constants')

const serveStaticFile = async ({ file, extension, statusCode }, response) => {
  if (STATIC_EXTENSIONS.indexOf(extension) === -1) throw new Error('not_found')

  let fileHandle

  try {
    fileHandle = await open(`./public/${file}`, 'r')
    const staticFile = await fileHandle.readFile()

    return response.end(staticFile)
  } catch (error) {
    console.error(error)
    throw new Error('not_found')
  } finally {
    if (fileHandle) fileHandle.close()
  }
}

module.exports = serveStaticFile

Building in the responder

// initialisers/http.js
if (!process.env.PORT) require('dotenv').config()

const { createServer } = require('http')

const serveStaticFile = require('../lib/responder')

const { PORT, APP_NAME } = process.env

module.exports = () => {
  const server = createServer(async ({ url }, response) => {
    const urlTokens = url.split('.')
    const extension = urlTokens.length > 1 ? `${urlTokens[urlTokens.length - 1].toLowerCase().trim()}` : false
    const isRoot = [ '', '/' ].indexOf(url) > -1
    const path = isRoot ? '/index.html' : url

    try {
      return await serveStaticFile({ file: path, extension: isRoot ? 'html' : extension }, response)
    } catch (error) {
      console.error(error)
      return await serveStaticFile({
        file: '/error.html',
        extension: 'html',
        statusCode: 500
      }, response)
    }
  })

  server.on('request', ({ method, url }) => {
    const now = new Date()
    console.info(`=> ${now.toUTCString()} – ${method} ${url}`)
  })

  server.listen(PORT, () => {
    console.log(`=> ${APP_NAME} running on port ${PORT}`)
  })
}

Mimes! No, not the performers...

// lib/responder.js

// line 2, at the top:
const { lookup } = require('mime-types')

// line 15, inside serveStaticFile's try block:
const mime = lookup(extension)
if (!mime) throw new Error('not_found')

response.writeHead(statusCode || 200, {
  'Content-Type': mime
})
$ npm install mime-types --save

More about mime-types:

https://github.com/jshttp/mime-types

Well done, you made it! πŸ‘πŸ‘πŸ‘

See you next time where we look at Dynamic content and templating.

Β 

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

Telegram channel: https://t.me/frameworkless

Twitter: @mtimofiiv

frameworkless.js -> Part 1

By Mike Timofiiv

frameworkless.js -> Part 1

Check out https://frameworkless.js.org/course/1 for the whole presentation

  • 1,671