For the duration of this course, you will work in teams to develop a microblogging and social networking web application based loosely around Twitter. In this third project, you will work in teams of up to four students to create a RESTful API using Node.js, Express, and MongoDB. You will gain experience programming in JavaScript, querying MongoDB, and using asynchronous programming techniques.
SlugFest is said to be the next hit social media site1—you just have to create it first! Members can post short messages ("slimes") for their followers to read. Members can also "favorite" slimes that they like or even "reslime" so that the post shows up on their own profile. Additionally, this is a social site, so it is possible to reply to slimes creating a "slime trail" conversation. If these features sound familiar, that's because they are based off of those supported by a popular microblogging and social media site.
Your task is to build all of the necessary backend functionality to support the pages you developed for Projects 1 and 2. This project spec contains a nontrivial RESTful API specification for you to implement. To receive the highest possible grade on this project, your team must also refactor code from Project 2 to interface with your programmatic backend. Note, however, that it is possible to receive a 3.75 on this project without updating your frontend.
You are provided with an intial skeleton of an npm
package to use for this project. After downloading, be sure to
run npm install
from within the package directory to
install all dependencies from the package.json
file.
You are also provided with the profile pictures for users of SlugFest. Skeleton HTML files are also provided, should you wish to use them (you may also use your own code).
Make sure to create a .env
file with your MongoDB
connection details. Recall that you should not be sharing these
with anyone. You will lose points if you turn in your
.env
file. It is possible to create
additional users in the MongoDB interface if your group would like
to share a database for testing. The format of this file should
be as follows (note the lack of spaces around the assignment
operator):
DB_USER=username
DB_PASS=password
DB_HOST=hostname
The starter code assumes that you will be using the
Project3
database to store all of your collections.
You do not need to change this unless it conflicts with your
extant databases on MongoDB Atlas.
Thes files may be downloaded here. The contents of this file is:
unzip -l p3-starter-code.zip
(out)Archive: p3-starter-code.zip
(out) Length Date Time Name
(out)--------- ---------- ----- ----
(out) 0 11-22-2021 15:50 p3-starter-code/
(out) 0 11-22-2021 15:47 p3-starter-code/models/
(out) 1088 11-22-2021 15:50 p3-starter-code/models/db.js
(out) 0 11-22-2021 15:59 p3-starter-code/public/
(out) 1850 11-22-2021 15:37 p3-starter-code/.gitignore
(out) 705 11-22-2021 15:41 p3-starter-code/package.json
(out) 0 11-22-2021 15:37 p3-starter-code/views/
(out) 0 11-22-2021 15:50 p3-starter-code/routes/
(out) 166 11-22-2021 15:51 p3-starter-code/routes/api.js
(out) 2924 11-22-2021 15:53 p3-starter-code/app.js
(out) 0 11-22-2021 15:59 p3-starter-code/public/skeletons/
(out) 3809 11-22-2021 15:59 p3-starter-code/public/skeletons/feed.html
(out) 5615 11-22-2021 15:59 p3-starter-code/public/skeletons/profile.html
(out) 2940 11-22-2021 15:59 p3-starter-code/public/skeletons/notifications.html
(out) 3045 11-22-2021 15:59 p3-starter-code/public/skeletons/slime.html
(out) 0 11-22-2021 15:59 p3-starter-code/public/assets/
(out) 6148 11-22-2021 15:59 p3-starter-code/public/assets/.DS_Store
(out) 0 11-22-2021 15:59 p3-starter-code/public/assets/profile/
(out) 249750 11-22-2021 15:59 p3-starter-code/public/assets/profile/kevin.jpeg
(out) 79031 11-22-2021 15:59 p3-starter-code/public/assets/profile/choong-soo.png
(out) 75662 11-22-2021 15:59 p3-starter-code/public/assets/profile/ed.png
(out) 18900 11-22-2021 15:59 p3-starter-code/public/assets/profile/lisa.jpeg
(out)--------- -------
(out) 451633 22 files
You must write a multi-paragraph report for Project 3. This report should be at least one page long and no more than five pages long. The goal is not to impress your instructor with your brevity or detail, but rather to clearly and effectively communicate your ideas.
Your report should address the following points:
As befitting a 300-level elective, this assignment is fairly open-ended. There is no "one true path" to creating and styling the required pages. Thus, this project requires you to tap your inner creativity and problem-solving.
Under-specification is fairly indicative of the real world. Clients will often expect you to "read their mind" to deliver a product that meets their needs. This can definitely be frustrating at times, but it is also a good lesson to learn early. It will help you be a high-quality developer who gains a positive reputation with clients.
A keen eye for detail is also key here. While there is textual description of each page provided, taking a close look at the provided images for styling and inferring withheld information can help you decide how to proceed.
As this is an upper-level course, you may use outside references to complete this assignment. Be sure to keep a running list of citations as you work on this project.
If you are sharing code using GitHub, be sure to make your repository private. Sharing your code in a public repository for this project is considered an honor code violation.
You will most certainly want to split your code into multiple JavaScript files. There are many opportunities for code reuse across this project. Follow the principles we discussed in class for organizing your site using the Model-View-Controller design pattern. The less you mingle code between aspects of these parts of the backend, the less "speghetti code" you will likely produce.
As you build out the API, consider implementing aspects of the model and controller in pairs. That is, implement the endpoint for getting a slime at the same time you implement the code in your model to generate a slime object. Write this code in such a way that you can reuse it as much as possible. For example, if you need to modify or add to the JSON returned by MongoDB for a slime, consider splitting this into a separate function that can be used for many different operations. There are many different endpoints that generate slimes. Try to only extend/modify the slime object in one function.
The API is large enough that you will likely wish to create sub-routers for the different API subsections. You can create these just like we do the main router. There are examples of this in our ToDoApp and in the starter code for this project.
Be sure to ask lots of questions as you work on this project. Questions about the desired behavior are likely to receive more expressive answers than direct questions about the reference implementation (your clients won't have a reference impelmentation). Similarly, there are likely many mistakes in this specification document. If something does not make sense there is a non-zero possibility that it's a mistake!
You may want to look back at the Project 1 and Project 2 specifications for styling and API hints. Note that the data returned by the API in this assignment may not be quite the same as what was in Project 2. Some URIs and data formats may have changed a bit.
You will want to test your code as you proceed. The most direct means of doing this is with HTTP requests generated by ThunderClient in Visual Studio Code. You can keep a list of requests and run through these tests as you make additional updates.
You might also consider following a test-driven development approach in which you create your requests first and then implement your code to add support for these requests. This can work particularly well because it allows you to have a request for printing debugging information as you work.
There are more sophisticated testing techniques you could use.
These include writing shell scripts to run multiple
requests and compare results. Recall that you can generate HTTP
requests using the curl
command line utility. This
utility supports a cookie jar for storing session cookies
as the client. There is also a jq
utility that you
can download and install for parsing JSON in a shell script (i.e.,
to check that responses are correct).
As we have discussed on numerous occasions in class, there are many security concerns to consider when developing a web application. These become particularly important when collaborating with others and handling user passwords. First and foremost, do not use any personal passwords for this project (either to authenticate MongoDB) or for user accounts. We are not using all possible security measures (especially for user accounts on SlugFest, where passwords are sent over the network with cleartext). Therefore, it's best to assume that all of the passwords used on this project are compromised.
While passwords are sent in cleartext over the network (http does
not encrypt messages), you should not store passwords
directly in the database. Instead, store a hash of the
paswword in the database using the Argon2
library. We
will be covering this in class. If you are getting a head start,
you might just hard code an authenticated user to use until we
cover this material.
Similarly, you should not be sharing your .env
file.
If you wish to share a database for development, create additional
uses in the MongoDB Atlast user interface for all group members.
Note that you will lose points on this assignment if you turn in
your .env
file.
Be sure that you are submitting to the correct assignment on Gradescope! Further, be sure to make sure that you submit all required files. Don't wait until the last minute to submit (you can resubmit up to the deadline).
Only submit one project per group. You can select your group members after uploading your files to Gradescope.
Submit the following:
*.html
: all HTML files needed for your pages
*.js
: all JavaScript files needed for your pages
*.css
: any additional CSS files you may have needed.
package.json
: the npm
configuration file.
HTML
, TXT
, or PDF
file
containing your written report and a list of citations for
resources you used to complete this project. No specific
convention is required, but this list should provide sufficient
information to find a copy of each resource.
.env
file.
node_modules
directory.
Your code should work with version 14 of Node.js. I will view your
files by node
on your main app.js
file
as we have done in class:
DEBUG=project3 node app.js
Thus, make sure that your files are submitted with a folder structure identical to your development machine. You will likely want to use relative URIs for assets.
I will be using my own MongoDB instance to test your code.
Therefore, be sure to use a .env
file, but do
not submit this file.
P2 Grading (out of 100 points):
async
/await
)
ejs
as it is reasonably simple to use for this project.
.env
fileThe API provided by SlugFest is based on Twitter's v1.1 API. One goal of this project is to give you exposure to real software and understanding how applications you might use on a daily basis actually function. We have made certain simplifications or adapations for ease of use.
All APIs are available at the api/
endpoint of the
server. For example, the statuses API
should be available at api/statues
.
Where indicated, these APIs require that a user be authenticated before use. Authenticating a user requires validating that a user's screen name and password match the information on file. Do not store passwords as plain text. Your project will receive a grade no higher than 50% if you do this.
Once a user is authenticated, the API server should remember the user through a session cookie. As long as this cookie remains valid, the user remains authenticated.
The following API endpoints can be used to log in and log out with user information.
Authenticates a user using the username
and
password
provided as parameters with the request.
Returns a welcome message if authentication is successful or an error message in the case of an authentication failure.
Name | Required | Description |
---|---|---|
username | required | The username (screen name) of the authenticating user. |
password | required | The password of the authenticating user. |
Deauthenticates an authenticated user. Returns a farewell message upon success or a description of the error upon failure.
Name | Required | Description |
---|---|---|
There are no parameters for this request |
All SlugFest APIs that return slimes provide that data encoded using JavaScript Object Notation (JSON). JSON is based on key-value pairs, with named attributes and associated values. These attributes, and their state are used to describe objects.
At SlugFest we serve many objects as JSON, including slimes and users. These objects all encapsulate core attributes that describe the object. Each slime has an author, a message, a unique ID, a timestamp of when it was posted, and sometimes geo metadata shared by the user. Each User has a SlugFest name, an ID, a number of followers, and most often an account bio.
With each slime we also generate "entity" objects, which are arrays of common slime contents such as hashtags, mentions, media, and links.
{
"created_at": <date/time in UTC format>,
"id_str": <id from db>,
"text": <slime text>,
"user": <user object>,
"in_reply_to_status_id_str": <id of the replied-to slime>,
"in_reply_to_user_id_str": <user id of the replied-to slime>,
"reslimed_status_id_str": <id of the reslimed status>
"reslimed_status": <slime object>,
"reply_count": <integer>,
"reslime_count": <integer>,
"favorite_count": <integer>,
"reslimed": <boolean>,
"favorited": <boolean>,
"entities": {
"hashtags": [],
"urls": [],
"user_mentions": [],
"media": []
}
Slimes are the basic atomic building block of all things SlugFest.
Slimes are also known as "status updates." The slime object has a
long list of 'root-level' attributes, including fundamental
attributes such as id_str
, created_at
,
and text
. Slime objects are also the 'parent' object
to several child objects. Slime child objects include user
,
entities
.
Attribute | Type | Description |
---|---|---|
created_at | String | UTC time when this slime was created. |
id_str | String | The string representation of the unique identifier for this slime. |
text | String | The actual UTF-8 text of the status update. |
user | User object | The user who posted this slime. See User data dictionary for complete list of attributes. |
in_reply_to_status_id_str | String | Nullable. If the represented slime is a reply, this field will contain the string representation of the original slime's ID. |
in_reply_to_user_id_str | String | Nullable. If the represented slime is a reply, this field will contain the string representation of the original slime's author ID. This will not necessarily always be the user directly mentioned in the slime. |
reslimed_status_id_str | String | Nullable. If the represented slime is a reslime, this field will contain the string represenataion of the original slime's ID. Note that this will always be an original slime object (not another reslime). |
reslimed_status | Slime object | Users can amplify the broadcast of slimes authored by other users by resliming . Reslimes can be distinguished from typical slimes by the existence of a reslimeed_status attribute (and the reslimed_status_id_str attribute). This attribute contains a representation of the original slime that was reslimed. Note that reslimes of reslimes do not show representations of the intermediary reslime, but only the original slime. (Users can also unreslime a reslime they created by deleting their reslime.) |
reply_count | Int | Number of times this slime has been replied to. |
reslime_count | Int | Number of times this slime has been reslimed. |
favorite_count | Int | Number of times this slime has been favorited. |
reslimed | Boolean | Nullable. Indicates whether this slime has been reslimed by the authenticating user. |
favorited | Boolean | Nullable. Indicates whether this slime has been favorited by the authenticating user. |
entities | Entities object | Entities which have been parsed out of the text of the slime. Additionally see Entities object. |
{
"id_str": <id of user>,
"name": <display name>,
"screen_name": <the username handle>,
"description": <short bio>,
"followers_count": <int>,
"friends_count": <int>,
"favorites_count": <int>,
"statuses_count": <int>,
"profile_image_url": <url for profile image>
}
The User object contains SlugFest User account metadata that
describes the SlugFest User referenced. Users can author slimes,
reslime, reply to slimes, follow Users,
and be @mentioned
in slimes.
Attribute | Type | Description |
---|---|---|
id_str | String | The string representation of the unique identifier for the user. |
name | String | The name of the user, as they’ve defined it. Not
necessarily a person’s name. Typically capped at 50
characters, but subject to change. If the user does not
specify a name, then this is their screen_name . |
screen_name | String | The screen name, handle, or alias that this user
identifies themselves with. screen_names are unique but
subject to change. Use id_str as a user identifier whenever
possible. |
description | String | Nullable. The user-defined UTF-8 string describing their account. |
followers_count | Int | The number of followers this account currently has. Under certain conditions of duress, this field will temporarily indicate 0. |
friends_count | Int | The number of users this account is following (AKA their “followings”). Under certain conditions of duress, this field will temporarily indicate 0. |
favorites_count | Int | The number of slimes this user has favorited in the account's lifetime. |
statuses_count | Int | The number of slimes (including reslimes) issued by the user. |
profile_image_url | String | A URL endpoint pointing to the user's profile image. This may be appended to the main host url for the web server to access the image. |
{
"hashtags": [],
"urls": [],
"user_mentions": [],
"media": []
}
Entities provide metadata and additional contextual information
about content posted on SlugFest The entities
section
provides arrays of common things included in slimes: hashtags,
user mentions, links, and attached media. These arrays are
convenient for developers when ingesting Slimes, since SlugFest has
essentially pre-processed, or pre-parsed, the text body. Instead
of needing to explicitly search and find these entities in the
slime body, your parser can go straight to this JSON section and
there they are.
Attribute | Type | Description |
---|---|---|
hashtags | Array of Hashtag Objects | Represents hashtags which have been parsed out of the slime text. |
urls | Array of URL Objects | Represents URLs included in the text of a slime. |
user_mentions | Array of User Mention Objects | Represents other SlugFest users mentioned in the text of the slime. |
media | Array | This array is unused and should be left empty. |
{
"indices": [<array of two ints>],
"text": <String>
}
The entities
section will contain a
hashtags
array containing an object for every hashtag
included in the slime body, and include an empty array if no
hashtags are present.
Attribute | Type | Description |
---|---|---|
indices | Array of Int | An array of integers indicating the offsets within the slime text where the hashtag begins and ends. The first integer represents the location of the # character in the slime text string. The second integer represents the location of the first character after the hashtag. Therefore the difference between the two numbers will be the length of the hashtag name plus one (for the '#' character). |
text | String | Name of the hashtag, minus the leading '#' character. |
{
"indices": [<array of two ints>],
"url": <String>
}
The entities
section will contain a urls
array containing an object for every link included in the slime
body, and include an empty array if no links are present.
Attribute | Type | Description |
---|---|---|
indices | Array of Int | An array of integers representing offsets within the slime text where the URL begins and ends. The first integer represents the location of the first character of the URL in the slime text. The second integer represents the location of the first non-URL character after the end of the URL. |
url | String | Wrapped URL, corresponding to the value embedded directly into the raw Tweet text, and the values for the indices parameter. |
{
"indices": [<array of two ints>],
"id_str": <String>,
"name": <String of the display name>,
"screen_name": <String of the username>
}
The entities
section will contain a
user_mentions
array containing an object for every
user mention included in the Tweet body, and include an empty
array if no user mention is present.
Attribute | Type | Description |
---|---|---|
indices | Array of Int | An array of integers representing the offsets within the slime text where the user reference begins and ends. The first integer represents the location of the '@' character of the user mention. The second integer represents the location of the first non-screenname character following the user mention. |
id_str | String | ID of the mentioned user, as a string. |
name | String | Display name of the referenced user. |
screen_name | String | Screen name of the referenced user. |
The following API endpoints can be used to programmatically create, retrieve and delete slimes and reslimes.
Access to this API requires that the user be authenticated.
Updates the authenticating user's current status, also known as composing a Slime. This request responds with the created slime object.
Name | Required | Description |
---|---|---|
status | required | The text of the status update. |
reply_to | optional | The ID of an existing status that the update is in reply to. |
Returns a single slime, specified by the ID parameter. The slime's auther will also be embedded withing the slime.
Name | Required | Description |
---|---|---|
There are no parameters for this request |
Destroys the status specified by the required ID parameter. The authenticating user must be the author of the specified status. Returns the destroyed status if successful.
Name | Required | Description |
---|---|---|
There are no parameters for this request |
Returns an array of slimes that are replies to the slime specified by the ID parameter. Each slime's author will be embedded within each slime.
Name | Required | Description |
---|---|---|
count | optional | The number of most recent replies to return (defaults to all reslimes). |
Returns an array of slimes that are reslimes of the slime specified by the ID parameter.
Name | Required | Description |
---|---|---|
count | optional | The number of most recent reslimes to return (defaults to all reslimes). |
Reslimes a slime as specified by the ID parameter. Returns the original slime with reslime details embedded.
Name | Required | Description |
---|---|---|
There are no parameters for this request |
Unreslimes a reslime as specified by the ID parameter. Returns the original slime with reslime details embedded.
Name | Required | Description |
---|---|---|
There are no parameters for this request |
Returns a collection of the most recents slimes posted by the user indicated by the
screen_name
or user_id
parameter. If no
user data is specified, return the timeline for the authenticated
user.
The timeline returned is the equivalent of the one seen as a user's profile on SlugFest.
Name | Required | Description |
---|---|---|
user_id | optional | The ID of the user for whom to return results |
screen_name | optional | The screen name of the user for whom to return results |
count | optional | Specifies the number of slimes to try to retrieve. By default, all slimes are returned. |
Returns a collection of the most recent slimes and reslimes posted by the authenticating user and the users they follow. The home timeline is central to how most users interact with the SlugFest service.
Name | Required | Description |
---|---|---|
count | optional | The number of slime objects to return (defaults to all slime objects) |
Returns the most recent slimes authored by the authenticating user that have been reslimed by others. This timeline is a subset of the user's timeline.
Name | Required | Description |
---|---|---|
count | optional | The number of slime objects to return (defaults to all slime objects) |
Returns the most recent slimes that are in reply to the authenticating user's slimes or are reslimes of the authenticating user's slimes. This is useful as a list of notifications to the authenticating user.
Name | Required | Description |
---|---|---|
count | optional | The number of slime objects to return (defaults to all slime objects) |
The following API endpoints can be used to programmatically create, retrieve, and delete favorites.
Access to this API requires that the user be authenticated.
Returns a collection of the most recents slimes liced by the user indicated by the
screen_name
or user_id
parameter. If no
user data is specified, return the favorites for the authenticated
user.
Name | Required | Description |
---|---|---|
user_id | optional | The ID of the user for whom to return results |
screen_name | optional | The screen name of the user for whom to return results |
count | optional | Specifies the number of slimes to try to retrieve. By default, all slimes are returned. |
Favorites (likes) the slime specified in the ID parameter as the authenticating user. Returns the favorite slime when successful.
Name | Required | Description |
---|---|---|
id | required | The string ID of the slime to like. |
Unfavorites (un-likes) the slime specified in the ID parameter as the authenticating user. Returns the unfavorited slime when successful.
Name | Required | Description |
---|---|---|
id | required | The string ID of the slime to like. |
The following API endpoints can be used to programmatically follow and unfollow users.
This API requires that the user be authenticated.
Allows the authenticating user to follow (friend) the user specified in the ID parameter.
Returns the followed user when successful. Returns a string describing the failure condition when unsuccessful. If the user is already friends with the user a HTTP 403 may be returned, though for performance reasons this method may also return a HTTP 200 OK message even if the follow relationship already exists.
Name | Required | Description |
---|---|---|
user_id | optional | The ID of the user for whom to return results |
screen_name | optional | The screen name of the user for whom to return results |
Allows the authenticating user to unfollow the user specified in the ID parameter.
Returns the unfollowed user when successful. Returns a string describing the failure condition when unsuccessful.
Name | Required | Description |
---|---|---|
user_id | optional | The ID of the user for whom to return results |
screen_name | optional | The screen name of the user for whom to return results |
The following API endpoints can be used to programmatically create, get, and update user information.
Use of this API (except for user/create.json) requires that the user be authenticated.
Allows a user to create a new SlugFest user. When creating a user, duplicate screen names are not allowed.
Returns the created user when successful. Returns a string describing the failure condition when unsuccessful.
Note that unlike other enpoints in this API, this endpoint does not require user authentication.
Name | Required | Description |
---|---|---|
username | required | The screen name of the user to create. |
password | required | The password for the user being created. |
Returns information about a user
specified by the user_id
or screen_name
parameter. If no parameter is specified, return the authenticated
user.
Name | Required | Description |
---|---|---|
user_id | optional | The ID of the user for whom to return results |
screen_name | optional | The screen name of the user for whom to return results |
Update the "display name" for the authenticating user as specified
by the display_name
parameter.
Returns the user object with the updated information upon success.
Name | Required | Description |
---|---|---|
display_name | required | The new display name for the user. |
Update the "description" for the authenticating user as specified
by the description
parameter. This is the brief bio
that shows up on a user's profile.
Returns the user object with the updated information upon success.
Name | Required | Description |
---|---|---|
description | required | The new description for the user. |
Update the profile image for the authenticated user. Note that this method expects raw multipart data, not a URL to an image. This method processes the uploaed file before updating the user's profile image URL.
Returns the user object with the updated information upon success.
This endpoint is bonus. You will need to research how to encode and upload/save images. Should you choose to implement this feature, your report should describe the process in detail.
Name | Required | Description |
---|---|---|
image | required | The avatar image for the profile, base64-encoded. Must be a valid GIF, JPG, or PNG image. |
The initial concept for this project is borrowed from an assignment designed by Andrew DeOrio at the University of Michigan.
Text from the API section was lifted and adapted directly from the Twitter Developer Platform.
1By your professor