Assignment 2: API Implementation Due Friday Feb 19, 10:00pm EST
We’ve got some good news, and some bad news. The good news is that thanks to your design documentation efforts, we now understand Avery’s mess of code much better. The bad news is that we’re going to need you to implement even more functionality to really support multiple rooms in Covey.Town’s backend server. In design review, we found the following key issues that weren’t addressed by the spec that we gave you in Assignment 1:
- There is no way to list which rooms are available for users to join
- There is no way to tell if a user is joining a room that already existed (perhaps one where they agreed to meet their friends in), or if the room was created on the spot (perhaps they made a typo in the room name)
- Multiple users might want to use the same “friendly name” for their room, like “Coffee Break” - but the design spec only allows for one room with each name.
In light of these concerns, we’ve sketched out the design for a new, RESTful API for the Covey.Town Room Service. This API consists of two resources:
/rooms
a resource that represents the different rooms available in Covey.Town/sessions
a resource that represents a player’s session in a given room. When a player joins a room, they start a new session.
By separating the resource of rooms
and sessions
, we can de-couple the process of joining a room (acquiring a session in that room) and creating the room. We’re also going to add three new important properties to our room
resource (and, correspondingly, to our CoveyRoomController
class): friendlyName
(a non-unique name that describes the room), isPubliclyListed
(a boolean value that determines if the room should be listed in the public index of rooms) and roomUpdatePassword
(a secret string that is generated by the server and returned to the client that creates the room, allowing the room’s friendlyName
and isPubliclyListed
fields to be updated in the future).
The API will support the following operations:
POST /rooms
- Create a new room. Requires a request body containing the propertiesfriendlyName: string
andisPubliclyListed: boolean
. Returns a JSON response body of format{coveyRoomID: string, coveyRoomPassword: string}
PATCH /rooms/:roomID
- Update the room with a givencoveyRoomID
. Requires a request body containing the propertiescoveyRoomID: string, coveyRoomPassword: string
, and the data being changed: one or both offriendlyName: string
,isPubliclyListed: boolean
.GET /rooms
- List all rooms that haveisPubliclyListed === true
. Returns a JSON response body of format{rooms: {friendlyName: string, coveyRoomID: string}[]}
DELETE /rooms/:roomID/:coveyRoomPassword
- Delete a room with a givencoveyRoomID
if it is protected by the room update passwordcoveyRoomPassword
.POST /sessions
- Create a new session. (This is comparable to the oldroomJoinRequest
from HW1).
Your assignment will be graded following the rubric embedded in this document, which will consist of the marks “Satisfactory,” “Meets Minimum Expectations,” and “Not Acceptable.” Based on past experiences, we project that this assignment could take you up to 14 hours (depending on your prior preparation). We encourage you to start early so that you can post questions on Piazza, make the most use of our TAs’ tutorials, and attend office hours as necessary in order to ensure that you can reach Satisfactory marks across the board.
The objectives for this assignment, are to:
- Expand an existing API following the coding conventions set out in an existing codebase
- Practice writing asynchronous code, including making HTTP requests
- Read the documentation for a third-party library to learn how to call it
- Relate architectural design principles to the design of a real codebase
Parts 1 and 2 are coding tasks, and you will implement them by modifying the handout code that we provide with this assignment. Part 3 of this assignment should be completed in a text editor or word processor, and submitted as a PDF.
This is an individual assignment.
Please post any questions about this assignment on Piazza.
Change Log
- 2/5: Initial Release
- 2/10: Added hint in part 2 about response types - JSB
- 2/11: Added warning to not hardcode
http://localhost:8081
in the API client - JSB
Part 1:
Avery has provided you with a sketch of the API that you should be implementing, and quite helpfully, has provided type definitions for the various API calls. Start by downloading this starter code Extract this archive, run npm install
in it to fetch all of the dependencies, and open the code in your IDE to start to get a handle on what Avery did here. To help you set up a local development environment for this class, we’ve prepared a tutorial for setting up a development environment with NodeJS, VSCode and TypeScript.
Updating the CoveyRoomsStore
Avery has already modified the CoveyRoomController
to generate and keep track of the ID and update password for each room. Avery is using the nanoid library to generate random strings for both the ID and password, which should ensure that IDs are unique, and that passwords are hard to guess. These values are automatically generated in the constructor of CoveyRoomController
, so you won’t need to worry about this aspect of the design. Avery has also stubbed out functions in CoveyRoomStore
for creating, updating, listing and deleting rooms. You should begin your implementation with these functions in CoveyRoomStore.ts
:
/**
* Returns a list of all of the publicly-visible rooms, representing each room as
* {friendlyName: string, coveyRoomID: string}
*/
getRooms(): CoveyRoomList
/**
* Creates a new room and returns the room's `CoveyRoomController`.
*
* @param friendlyName
* @param isPubliclyListed
*/
createRoom(friendlyName: string, isPubliclyListed: boolean): CoveyRoomController
/**
* Updates the friendlyName and/or public visibility of a room if there is a room that has the
* specified ID and password. Only updates fields that are passed (note friendlyName or makePublic
* might be undefined). Returns true if a room was found matching the ID and password, or false
* otherwise. If there are no updates (e.g. friendlyName === undefined && makePublic === undefined),
* but the room ID and password are valid, this method should still return true.
*
* @param coveyRoomID
* @param coveyRoomPassword
* @param friendlyName
* @param makePublic
*/
updateRoom(coveyRoomID: string, coveyRoomPassword: string, friendlyName?: string, makePublic?: boolean): boolean
/**
* Deletes the room specified by ID. Only deletes the room if the password specified as a
* parameter here matches the password stored by the matching CoveyRoomController. This method
* should both remove the room from the RoomStore's listing of rooms, and also disconnect
* any players from the room (by calling CoveyRoomController.disconnectAllPlayers).
*
* Returns true if the room was found, password matched, and the room was deleted. Otherwise
* returns false.
*
* @param coveyRoomID
* @param coveyRoomPassword
*/
deleteRoom(coveyRoomID: string, coveyRoomPassword: string): boolean
Avery has included a thorough test suite for CoveyRoomsStore
(CoveyRoomsStore.test.ts
), which you can run for yourself. Once you believe that you have implemented these functions correctly, check your progress by running npm test
. You should make sure that you pass all of these tests before your proceed.
Defining the REST API
As in your onboarding assignment, the file src/router/room.ts
contains all of the HTTP routes for the /rooms
and /sessions
APIs. Avery has added routes to the express server for all of the new API functions listed above except for DELETE /rooms
. Add a route to room.ts
, connecting from DELETE /rooms/:roomID/:roomPassword
to the roomDeleteHandler
. Follow the pattern that Avery used in the other request handlers for calling the handler, checking for errors, and sending the response back to the client.
Implementing the request handlers
As in your onboarding assignment, the file src/requestHandlers/CoveyRoomRequestHandlers.ts
contains the request handlers that process each client’s request and format a response. To help future-proof the API and make it easier to pass metadata back to the client, each request handler now returns a ResponseEnvelope
, which consists of a boolean field (isOK
), an optional status message (message
), and an optional response payload (response
) . When you implement your REST client, you’ll find that using a standard approach to pass errors like this is quite useful.
There are four handlers that are entirely unimplemented. Your task is to implement all four of these handlers following the specification, using the existing roomJoinHandler
as an example.
/**
* List all of the rooms that are set to "publicly visible"
*
* The `isOK` field on the envelope must be set to `true` in all cases
* (this function can not return an error)
*
* @see CoveyRoomsStore.getRooms - which will return the list of rooms
*
*/
export async function roomListHandler(): Promise<ResponseEnvelope<RoomListResponse>>
/**
* Create a new room and returns its ID and password.
*
* Sets the `isOK` field on the envelope to `false` if the `friendlyName` specified is empty
* (a 0-length string), and also sets the `message` envelope field with a descriptive error.
*
* Otherwise, sets the `isOK` field on the envelope to `true` if the request succeeds, and returns
* the room information inside of the response envelope.
*
* @see CoveyRoomsStore.createRoom - which will create and track the new room
*
* @param requestData the "friendly name" to assign this room, and its publicly visibility
*
*/
export async function roomCreateHandler(requestData: RoomCreateRequest): Promise<ResponseEnvelope<RoomCreateResponse>>
/**
* Deletes a room.
*
* Sets the `isOK` field on the envelope to `true` if the room exists, password matches, and room
* is deleted. Sets the `isOK` field on the envelope to `false` and the `message` field to "Invalid
* Request" if the room does not exist, or password does not match.
*
*
* Does not return any other data inside of the envelope
*
* @see CoveyRoomsStore.deleteRoom - which will delete the room from its store
*
* @param requestData the requested room ID to delete and the password specified by the client
*/
export async function roomDeleteHandler(requestData: RoomDeleteRequest): Promise<ResponseEnvelope<Record<string, null>>>
/**
* Updates a room's friendlyName and/or public visibility.
*
* Rejects the request (by setting `isOK` field on the envelope to `false`) if the request is to
* update the `friendlyName` to an empty string.
*
* Sets the `isOK` field on the envelope to `true` if the room exists, password matches, and room
* is updated. Sets the `isOK` field on the envelope to `false` and the `message` field to "Invalid
* Request" if the room does not exist, or password does not match.
*
* @see CoveyRoomsStore.updateRoom - which will update the room's data
*
* @param requestData the update request. This handler should only update fields of the room only
* if the password supplied in the request matches the password on record.
*/
export async function roomUpdateHandler(requestData: RoomUpdateRequest): Promise<ResponseEnvelope<Record<string, null>>>
You can test your request handlers in a few ways:
- By using Postman or Curl, as described in the week 3 tutorial
- By continuing the assignment to Part 2: implementing the REST client, and using your client to test your server
- By submitting your code to GradeScope, the autograder will test your server code. It will test your server using our reference client.
Rubric Specification for Part 1
Part 1 will account for 1/3 of your overall grade on this assignment.
To receive a mark of “Satisfactory” for Part 1, your code submission must:
- Pass all included automated tests as reported by
npm test
(the tests inCoveyRoomStore.test.ts
) - Pass all of the automated tests run by GradeScope (which are not included in the handout)
- Follow the design specification outlined above
- Conform to our style guide and have no style warnings or errors as reported by
npm run-script lint
- Have no
@ts-ignore
oreslint-disable
annotations in the code that you write
To receive a mark of “Meets minimum expectations” for Part 1, your code submission must:
- Pass all included automated tests as reported by
npm test
(the tests inCoveyRoomStore.test.ts
). Other tests may fail. - Have no style errors (may have warnings) as reported by
npm run-script lint
- Have no
@ts-ignore
oreslint-disable
annotations in the code that you write
Warning Submissions that do not meet the above criteria will receive no credit for Part 1. Do not wait to test or style check your code until the last minute.
Part 2
Your next task is to implement a client for the REST API that you created in the first part of this assignment. The client should be implemented in the file src/client/RoomServiceClient.ts
and use the axios library to make requests to the server. Avery has stubbed out the client, and even added boilerplate code to create the axios client, and set the baseURL
property on it. Implement each one of the API client methods using the _axios
client. Making a call of _axios.get('/endpoint');
will result in a GET
request to /endpoint
on your server, _axios.post('/endpoint', {foo: 'bar'})
will result in a POST
request to /endpoint
, passing the request body {foo: 'bar'}
. Important (2/11): When we test your API client, we will not be running the server at localhost:8081
- so do not hard code that into your axios requests: please follow this syntax (otherwise the tests will all fail with ECONNREFUSED 127.0.0.1:8081
).
Refer to the axios documentation, and use other internet sources to learn how to use this library. Hint: Here is a code snippet that makes an HTTP POST
request to the endpoint /endpoint
, passing requestData
, and unwrapping the response as a MyResponseType
: const response = await this._axios.post<MyResponseType>('/endpoint', requestData);
.
Hint (2/10): The return type that you should be telling Axios to expect (MyResponseType
in the prior hint) should be the exact same return type as the type returned by your corresponding request handler. TypeScript will not throw an error at runtime if you choose the wrong type, and instead there will be an error that will come from one (or all) of our tests failing since the data is malformed.
We have created an automated test suite to evaluate the functionality of your API client, and this test suite will run when you submit your assignment to GradeScope.
However, we strongly encourage you to test your API client locally (on your own machine), so that you can find and fix errors faster than it would take to zip your code, upload it to GradeScope, and receive results.
You can manually test your API client and API server by:
- Start the server (
npm run start
) - Use the file
src/client/ClientExamples.ts
to manually execute the API. In a different terminal window (but in the same directory - the handout directory), run this file withnpx ts-node src/client/ClientExamples.ts
.
GradeScope will automatically test your client using our reference server. Hence, you can receive full marks on this part even if your server is not functional, as long as your client implements the specification.
Rubric Specification for Part 2
Part 2 will account for 1/3 of your overall grade on this assignment.
To receive a mark of “Satisfactory” for Part 2, your code submission must:
- Pass all of the automated tests run by GradeScope (which are not included in the handout). All tests must pass.
- Follow the design specification outlined above
- Conform to our style guide and have no style warnings or errors as reported by
npm run-script lint
- Have no
@ts-ignore
oreslint-disable
annotations in the code that you write
To receive a mark of “Meets minimum expectations” for Part 2, your code submission must:
- Pass all of the automated tests run by GradeScope (which are not included in the handout). All tests must pass.
- Have no style errors (may have warnings) as reported by
npm run-script lint
- Have no
@ts-ignore
oreslint-disable
annotations in the code that you write
Warning Submissions that do not meet the above criteria will receive no credit for Part 2. Do not wait to test or style check your code until the last minute.
Part 3: Software Architecture Research
Avery is somewhat concerned that Covey.Town’s launch could be an “epic fail:” if thousands of people try to connect to it, will the system handle the load, or will it grind to a halt, crash, and burn? While browsing around on Twitter, Avery found a blogpost about software architectures for massively multiplayer multi-user environments. Read this blogpost, and based on its contents, answer the following questions related to Covey.Town’s architecture and performance. Please keep each answer to within 2-4 sentences, and be sure to relate your answer to the blogpost.
- What aspects of Covey.Town involve sending N^2 network messages (with regards to N concurrent users)?
- How does the blog post suggest that multi-player games can improve responsiveness while still maintaining a consistent view of the world?
- One of the scaling approaches described in the blogpost is sharding. Given the system that you built in part 1 and 2, how would you suggest that we shard Covey.Town traffic across multiple servers?
- One day, we might want to allow more than 50 players to connect to the same room - perhaps, up to 1,000 in one room. How does the article suggest architecting communication in such a big room?
Rubric Specification for Part 3
Each of the four questions in Part 3 will account for 1/12 of your overall grade for this assignment (that is, Part 3 will account for 1/3 of the overall grade, with each question weighted evenly). Each of the four questions will be graded to the following specification:
To receive a mark of “Satisfactory” on a question:
- The answer is factually correct, using the terminology and rationales provided in the blogpost
- The provided explanation is 2-4 sentences, and relates the architectural concepts with the Covey.Town codebase
To receive a mark of “Meets minimum expectations” on a question:
- The answer is factually correct
- The provided explanation is 2-4 sentences and discusses either an architectural concept or the Covey.Town codebase, but may not link them together
Answers that do not meet the above criteria will receive no credit for that question.
Submission Instructions
Submit your assignment in GradeScope. The easiest way to get into GradeScope the first time is to first sign into Canvas and then click the link on our course for “GradeScope”. You should then also have the option to create an account on GradeScope (if you don’t already have one) so that you can log in to GradeScope directly. Please contact the instructors immediately if you have difficulty accessing the course on GradeScope.
Parts 1 and 2 should be submitted together on GradeScope. To submit Parts 1 and 2: run the command npm run-script pack
in your project directory, which will create a zip file that is properly structured for submission. Important: GradeScope only accepts .zip files, not .tgz files - if you run npm pack
, you will get a .tgz file, and it will not be accepted by GradeScope. Please be sure to run npm run-script pack
. Submit this zip file to the assignment “Homework 2 (Parts 1 & 2)” on GradeScope. GradeScope will provide you with feedback on your submission, providing a numeric score for Part 1 and Part 2 of:
- 2 (Satisfactory)
- 1 (Meets minimum expectations)
- 0 (Not passing)
In the “Autograder” score, you’ll see the sum of these two values. You can view the per-part grade and complete output from running the tests and linter on GradeScope. If you have any doubts about the autograder, please contact the course staff immediately. In particular: if you are not able to reproduce and debug test or linter failures on your local machine, please ask the TAs for assistance: otherwise you’ll waste an immense amount of time waiting for the autograder to complete, when you could get the same feedback in seconds running the tests + linter locally.
Part 3 should be submitted as a PDF on GradeScope to the assignment “Homework 2 (Part 3)”. Please use GradeScope’s “tagging” interface to associate each of your answers with the questions in the rubric.