Practical: Implement an In-Memory Server for Simplepedia
Initial Due Date: 2024-10-24 11:59PM Final Due Date: 2024-12-09 4:00PMGithub Classroom Gradescope
In this practical, you will use NextJS API routes to implement a simplified version of the Simplepedia API.
Learning Goals:
- Implement a simple NextJS server with API routes
- Learn how to test NextJS API routes
- Commit and push your changes to GitHub
- Submit your repository to the Gradescope assignment
Prerequisites
- 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.
- Clone the repository to you computer with
đź’» git clone
(get the name of the repository from GitHub). - Open up the
package.json
file and add your name as the author of the package. - Install the module dependencies by executing
đź’» npm 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.
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 💻 npm run dev
then open the application and the browser’s developer tools. In the console, paste and execute a test fetch
command. Hopefully see the expected sections array!
fetch("/api/sections")
.then(resp => resp.json())
.then(data => { console.log(data); });
Another option to test your API is to use an external application like postman. This gives you a nice UI to test APIs. If you choose to use postman, you’ll need to download the “postman desktop agent” to be able to test APIs running locally!
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 đź’» npm 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 create a consistent test environment (making the tests “Independent” and “Repeatable”), then execute a set of tests. Each of those tests executes 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.
Grading
Required functionality:
- Complete the Simplepedia API
- Pass all tests
- Pass all ESLint checks
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).