Implement an In-Memory Server for Simplepedia

Initial Due Date: 2025-04-03 9:45AM
Final Due Date: 2025-04-17 4:15PM

Goals

Prerequisites

  1. Create the git repository for your practical by accepting the assignment from GitHub Classroom. This will create a new repository for you with a skeleton application already setup for you.
  2. Clone the repository to you computer with đź’» git clone (get the name of the repository from GitHub).
  3. Open up the package.json file and add your name as the author of the package.
  4. Install the module dependencies by executing đź’» pnpm install in the terminal.

Background

We are going to use NextJS API routes to implement a simplified version of the Simplepedia API. Recall the Simplepedia API is:

Endpoint Method Action
/api/sections GET Fetch a JSON array of all sections in sorted order
/api/articles GET Fetch the entire article collection as an array sorted by title
/api/articles?section=:section GET Fetch all articles in the corresponding section sorted by title
/api/articles POST Add a new article to the collection (the new article should be provided as the JSON-encoded request body)
/api/articles/:id GET Get article with id of :id
/api/articles/:id PUT Update the article with id of :id (entire updated article, including id should be provided as the JSON-encoded request body)

Our server will use “in memory” data storage. In other words, when the server is started, it will read in the contents of seed.json, and store it in a Map with the id as the key and the article object as the value. Review data/articles.js to see how the global object is constructed. Changes will be made to this local copy of the data providing the appearance of persistence, but if the server is restarted, the server will return to the original copy of the data. For proper persistence, we will require some form of database, which we will discuss soon.

Recall that the files in the pages/api directory implement the server endpoints, i.e., NextJS executes this code on the server. not on the client (in the browser). NextJS routes requests to /api/... to the files in pages/api using its mapping rules (e.g., /api/articles/42) is mapped to api/articles/[id].js. These files don’t contain React components, instead the export handler functions that expect request and response objects as arguments and construct and appropriate response (typically JSON-encoded data).

We will implement these routes with next-connect so we can minimize the boilerplate needed for matching methods, etc. Recall from class that we construct the endpoint from a chain of .METHOD functions, e.g., .get for GET, that takes a function with the req request and res response objects. The first contains all the information sent in the request (and added by any middleware) and the second is used to construct the response. Note that invoking the response methods, e.g., res.end(...) does not end the function, you need to explicit terminate the function if you want it to end early.

Serving sections

In src/pages/api/sections.js we will implement the /api/sections endpoint. Where indicated by the “TODO” implement code to generate a sorted de-duplicated array of sections (i.e., first letter of the article upper-cased). You can then send this array (e.g., sections) as JSON to the requester with

res.status(200).json(sections);

Hopefully generating sections from the article collection is a familiar task from your programming assignment. Note that articles is a Map, not an array. The Map provides forEach method that can be used to iterate through all the values in the Map, e.g., articles.forEach((article) => ...).

You can test your API implementation using fetch via the browser’s console. Start the development server with 💻 pnpm run dev then open the application and your browser’s developer tools. In the console, paste and execute a test fetch command. Hopefully you see the expected sections array!

fetch("/api/sections")
   .then(resp => resp.json())
   .then(data => { console.log(data); });

In the test above, we are implementing minimal error handling. We will try to parse any response as JSON, including any error responses. To get more information about any errors, click over to the Network tab in the browser’s developer tools and click on the failing request. You want to view the full Response to see the complete error message.

Serving a single and multiple articles

In src/pages/api/articles/index.js we will implement the GET /api/articles endpoint. Where indicated by the TODO implement code to return an array of articles, potentially filtered by the section query parameter (i.e., by req.query.section), You can obtain the array of articles with Array.from(articles.values()). As above, you should send a response status of 200.

In src/pages/api/articles/[id].js we will implement the GET /api/articles/:id endpoint. Where indicated by the TODO implement code to return a single article with the corresponding id (i.e., with req.query.id). If the id is valid respond with a status code of 200 and the article as JSON. If the id is not valid (i.e., not present in the articles Map) respond with 400 error status code and corresponding message, e.g.,

res.status(400).end("Invalid article");

Recall that the article ids, and thus the Map keys are integers while the URL parameters, e.g., req.query.id are strings. Once you have implemented these endpoints, test them in the browser.

Review the other endpoint implementations

The skeleton includes code for the other endpoints (POST and PUT). Review those implementations for other examples of how to implement an in-memory server.

Unit testing

So far we have only performed ad-hoc testing the API with the browser. The skeleton also includes unit tests. Run the tests by executing 💻 pnpm test. The tests (in src/__tests__/api.test.js) are implemented with next-test-api-route-handler a library that makes it easier to unit test API routes (note we are using version 4, which is not compatible with version 3). You will see our familiar testing pattern, in which you define a test suite, use the beforeEach “setup” function to “arrange” a consistent test environment (making the tests “Independent” and “Repeatable”), then execute a set of tests. Each of those tests “acts” by executing a special fetch function, i.e. makes a HTTP request to the API, then makes a set of assertions about the response.

Finishing up

Commit any changes you may have made and then push your changes to GitHub. You should then submit your repository to Gradescope as described here.

Grading

Required functionality:

Recall that the Practical exercises are evaluated as “Satisfactory/Not yet satisfactory”. Your submission will need to implement all of the required functionality (i.e., pass all the tests) to be Satisfactory (2 points).