5-6 Implementing REST API

This lab consolidates programming knowledge learnt in previous lecture to build a simple Single Page Application, which is a web app that consists of two main parts:

  • server-side: a REST Web API provides endpoints for CRUD operations on products
  • client-side: a Vue application that interacts with the Web API

The client-side Vue application is identical to the one developed in section 5-4 ‘Making HTTP requests in clients’.

The server-side is built with Express.js.

Main program

The main program creates an Express app, binds the REST API implementation to the path /api, and serves the client-side Vue application as static files in /dist.

import express from 'express';
let app = express();

// import the router that implements the REST API
import api from './api.mjs';
app.use('/api', api);

// the Vue application built by Vite, 'npm run build'
// it consumes the REST API
app.use(express.static(__dirname + '/dist'));

const port = 8000;
app.listen(port);

REST API implementation

An Express Router is an object to group routes and middlewares. In this example, all requests with the prefix /api will pass through this router. Thus, app.get('/products', ..) handles requests for path /api/products. At the end of the module, we export the router object as default. Notice how the main project imports the router and adds it to the stack of middlewares of the Express app.

import express from 'express';
let api = express.Router();

api.get('/products', async (req, res) => {
  const q = "SELECT * FROM Product";
  try {
    const result = await db.all(q);
    res.json(result);
  } catch (err) {
    res.status(500).json(err);
  }
});

// other endpoints ...

export default api;

The route for GET /products uses a SELECT query to retrieve all products from the database, and if there are no run-time errors, return the result as JSON data to the client. In case there are errors (e.g. the promise db.all(q) is rejected), we set the HTTP status code to 500, and return the error object for debug purpose. (In production code, you should never return error object. This may expose confidential information about your application. Usually, you will create a user-friendly error page.)

The following is the route to handle POST /products. This route handler creates a product in the database. The data about the new product is passed to the API as JSON data. Therefore, the router has to use the express.json() middleware to parse the HTTP request payload and save the data in req.body. Next, basic sanity check is done on the user input, e.g. are all required fields of the new product provided? You may want to do further checking, e.g. are the values in a reasonable range. If there are error about input data, the route handler returns HTTP status code 400.

api.use(express.json());

api.post('/products', async (req,res) => {
  if (req.body.productName==undefined
    || req.body.category==undefined  
    || req.body.unitPrice==undefined 
    || req.body.unitsInStock==undefined)  { 
    return res.sendStatus(400); 
  }

  let valuesNewProduct = {
    $productName: req.body.productName,
    $category: req.body.category,
    $unitPrice: req.body.unitPrice,
    $unitsInStock: req.body.unitsInStock  
  };
  // todo: further checking on valid values

  const q1 = 'INSERT INTO Product (productName, category, unitPrice, unitsInStock) VALUES ($productName, $category, $unitPrice, $unitsInStock)';

  const q2 = "SELECT * FROM Product WHERE id=$id";

  try {
    const insResult = await db.run(q1, valuesNewProduct);
    const pid = insResult.lastID;
    let result = await db.get(q2, {$id: pid});
    res.status(200).json(result);
  } catch (err) {
    res.status(500).json(err);
  }
});

The database operations may also generate errors. These will be thrown as exceptions by awake. We catch these promise rejection errors and return the HTTP status code 500.

If there is no error, we return a JSON string of the newly created product to the client-side.

For the implementation of other routes in the REST API (e.g. PUT and DELETE), please refer to the source code.

Downloadable sample code

  • p560-restapi.zip - the Single Page Application example on this page, which contains both the client-side Vue app and the server-side REST Web API.