ExpressJS Webserver

Modern JavaScript

ExpressJS Webserver

In this unit, we will create a simple web server and API using ExpressJS. Express is a fast, web framework for Node.js. It is designed for building web applications and APIs. This video will walk you through getting started:

Show/Hide Video

Here is the code for our app.js file:

const express = require('express')
const app = express()
const port = 3000

app.listen(port, () => console.log(`Listening on port: ${port}`))

Handling Requests

Express allows us to handle different types of requests. We can use the app.get() method to handle GET requests. Take a look:

Show/Hide Video

Here are the routes that we added in the video:

app.get('/', (request, response) => {
    response.send('hello world')
})

app.get('/test', (request, response) => {
    response.send('hello from test route')
})

Responding with JSON

We can also respond with JSON data. Take a look:

Show/Hide Video

Here is the pokemon data that we added in the video:

const pokemon = [
    { id: 1, name: 'Bulbasaur', type: 'Grass' },
    { id: 2, name: 'Ivysaur', type: 'Grass' },
    { id: 3, name: 'Venusaur', type: 'Grass' },
    { id: 4, name: 'Charmander', type: 'Fire' },
    { id: 5, name: 'Charmeleon', type: 'Fire' },
    { id: 6, name: 'Charizard', type: 'Fire' },
    { id: 7, name: 'Squirtle', type: 'Water' },
    { id: 8, name: 'Wartortle', type: 'Water' },
    { id: 9, name: 'Blastoise', type: 'Water' },
]

Here is the code from the video:

// allow us to send json
app.use(express.json())

// and the two new routes
app.get('/api/v1/random', (request, response) => {
    const randomNumber = Math.floor(Math.random() * 100) + 1
    response.send({ randomNumber })
})

app.get('/api/v1/random-pokemon', (request, response) => {
    const r = Math.floor(Math.random() * 9)
    response.send(pokemon[r])
})

Responding with HTML

We can also respond with HTML. Take a look:

Show/Hide Video

Here is the code from the video:

// add path module and root of the static files
const path = require('path')
const root = path.join(__dirname, 'public')

// added middleware to serve static files
app.use(express.static('public'))

// updated the root route to serve an html file
app.get('/', (request, response) => {
    response.sendFile('index.html', { root })
})

Front and Back End Communication

Next, we will look at how to communicate between the front-end and back-end of our application. We will use the fetch API to make requests from the front-end to the back-end.

First we need to add a route to our back-end that will accept a parameter. Take a look:

Show/Hide Video

Here is the endpoint that we added in the video:

app.get('/api/v1/pokemon/:id', (request, response) => {
    const { id } = request.params
    const found = pokemon.find(p => p.id.toString() === id)
    if (found) response.send(found)
    else response.send({ error: { message: `Could not find pokemon with id: ${id}` }})
})

Now that our back-end is ready, we can make a request from the front-end. Take a look:

Show/Hide Video

Here is the code from the video:

Our new route for getting a specific pokemon:

app.get('/pokemon/:id', (request, response) => {
    response.sendFile('index.html', { root })
})

And here is our front-end code:

(async () => {
    const h2 = document.querySelector('h2')
    const h3 = document.querySelector('h3')

    const { pathname } = window.location
    const [, searchType, id ] = pathname.split('/')

    const url = searchType === 'pokemon'
        ? `/api/v1/pokemon/${id}`
        : '/api/v1/random-pokemon'
    
    const result = await fetch(url)
    const { name, type } = await result.json()

    h2.textContent = name
    h3.textContent = type
})()

Exercise 1

Show/Hide Video

For this exercise, create a new back end route that will return a random pokemon based on the type. You should be able to pass the type as a parameter in the URL. For example, /api/v1/random-pokemon/grass should return a random grass type pokemon.

Then create a new front end route that will display the name and type of the random pokemon that is returned. The route should be /type/:type.

Hints

How do I figure out which back-end point to call?

Before we used this code:

const url = searchType === 'pokemon'
    ? `/api/v1/pokemon/${id}`
    : '/api/v1/random-pokemon'

Now that we have a new route, you will need to update this code to call the correct route based on the type that is passed in the URL.

One way to do this is to add a new condition to the ternary operator that checks for the type route. You can then use the type to build the URL for the fetch request.

const url = searchType === 'pokemon'
    ? `/api/v1/pokemon/${id}`
    : searchType === 'type'
        ? `/api/v1/random-pokemon/${id}`
        : '/api/v1/random-pokemon'

That's pretty hard to read! So you might want to move it into a separate function to make it easier to understand.

const getUrl = (searchType, id) => {
    if (searchType === 'pokemon') return `/api/v1/pokemon/${id}`
    if (searchType === 'type') return `/api/v1/random-pokemon/${id}`
    return '/api/v1/random-pokemon'
}

Solution

Show the Answer

Here is the back-end code for the new route:

app.get('/api/v1/random-pokemon/type/:type', (request, response) => {
    const { type } = request.params
    const found = pokemon.filter(p => p.type.toLowerCase() === type.toLowerCase())
    const r = Math.floor(Math.random() * found.length)
    if (found.length > 0) response.send(found[r])
    else response.send({ error: { message: `Could not find pokemon with type: ${type}` }})
})

And here is the front-end code for the new route:

(async () => {
    const h2 = document.querySelector('h2')
    const h3 = document.querySelector('h3')

    const { pathname } = window.location
    const [, searchType, id ] = pathname.split('/')

    const url = (() => {
        if (searchType === 'pokemon') return `/api/v1/pokemon/${id}`
        if (searchType === 'type') return `/api/v1/random-pokemon/${id}`
        return '/api/v1/random-pokemon'
    })()
    
    const result = await fetch(url)
    const { name, type } = await result.json()

    h2.textContent = name
    h3.textContent = type
})()
Walkthrough Video

Rest Client

I want to show you a tool that I use to test my back-end routes. It's called REST Client. You can install it in Visual Studio Code. Here is a video that shows you how to use it:

Show/Hide Video

Here is the code that we used in the video:

### Variables
@url = http://localhost:3010/api/v1
@type = water


### Random Pokemon
GET {{url}}/random-pokemon


### Specific Pokemon
GET {{url}}/pokemon/5


### Random with type
GET {{url}}/random-pokemon/{{type}}

Post Requests

Finally, we will add a new endpoint that allows us to add a new pokemon to our list of pokemon. We'll use REST Client to test our new endpoint.

Show/Hide Video

Here is the code we added to our app.js file:

app.post('/api/v1/add', (request, response) => {
    const { id, name, type } = request.body
    const found = pokemon.find(p => p.id.toString() === id.toString())
    if (found) response.send({ error: { message: `Pokemon with id: ${id}, already exists`} })
    else pokemon.push({ id, name, type })
})

And here is the REST Client code we used to test our new endpoint:

### Add new pokemon
POST {{url}}/add
Content-Type: application/json

{ 
    "id": 10,
    "name": "Caterpie",
    "type": "Bug"
}