Assignment 3.1: Generic Social Media App

Written by Michael, with immense styling help and feedback from Kashif Nazir. Inspired by the Flutterer assignment from CS106AX

Due Thu May 18 11:59pm PT

Submissions not accepted after Sat May 20 11:59pm PT.

Backstory: You're building up quite the reputation around these parts! As you start to turn your attention back to fixing Axess, you get a message from [insert vague acquaintance]. They've got a great new startup idea: they're thinking now would be a great time to spin up a new social media platform, this time with [insert underspecified and probably not very useful feature]! They decide to start simple--there will be plenty of time to add the cool new feature after they get a few million users, surely. Unfortunately, they're a little shaky with JavaScript and APIs, so they've asked you for help. They say they'll repay you in stock options just as soon as they make it big...

For this assignment, you will implement the frontend logic for a social media-style app. The app will allow users to make posts and follow other users, showing them a feed of the posts from those they follow.

Learning goals

Through this assignment, you will

Overview

When you are finished, your app will look something like this:

Screenshot of complete app. See below for description
Sample of complete app. (Click to enlarge)

After logging into the app (there is no password or authentication), you are shown a feed of the posts made by users you follow. You can make a post, update your display name and avatar, and add/remove people you're following.

To get started, download the starter code, extract it, and run npm install as usual.

There are quite a few files in the public folder, organized into subfolders for css and js. All of the HTML and CSS are written for you (huge thanks to Kashif Nazir for the styling work!), so you should only modify the files in the js folder. However, you will need to know the structure of the HTML you are working with, and we encourage you to explore the CSS for some insights and clever tricks you may find helpful for your project and future work.

Part 1: apiRequest

First, you will implement apiRequest, a small wrapper function around fetch() that will simplify making API requests.

Setup: Accessing the API

You will be sending requests to an API running on our server. To get started, go to our API setup page, where you will be given a personalized API URL. This allows you to make edits to your own database, without conflicting with others. Write this URL into js/apirequest.js, and be sure that all requests you make to our server begin with that URL.

If you ever want to reset your database to its initial state, you can use the button on that page to do so.

Once you have filled in your personalized API URL, review the comment above the apiRequest function for its expected operation, and implement it accordingly. Here are a few more details:

  • The comment in apirequest.js, like the other comments in the provided files, is an interface comment, not an implementation comment. It describes how the function will be called rather than how it works. For example, its description of the path parameter means that the caller will always include a leading / and that your function should prepend API_URL.
  • We will not test your function on invalid arguments. This means, for example, that method will always be a valid HTTP method (and all uppercase), path will always start with a /, and body won't be passed if the method is GET.
  • You do not have to handle the case when the API is unreachable (e.g. no Internet) or doesn't return JSON. The respective calls to fetch or .json will throw an exception, which you don't have to handle.
  • We have written a few test cases in testApi in js/index.js. You can call testApi from the console to run them, and review the code in this function to understand the expected results.

  • apiRequest itself is not currently exposed to the console. You may want to do so in js/index.js for additional testing. (But don't do this in apirequest.js.)
  • In addition to the other /tests endpoints, you can make a request to /tests/echo, which will return a JSON object which includes the method, query string (as key/value pairs), and request body the server received.
  • When the browser makes an HTTP request that results in a non-200 status, it will print a message like GET [url] 404 (Not Found). This is just to let you know the response had an error status; it doesn't mean an exception was thrown or caught, or necessarily indicate a correct or incorrect handling. For example, the apiRequest call to /tests/error is meant to generate a 499 status. As long as you don't see another error message (e.g. "/tests/error didn't throw an error"), you should be handling it correctly.

Your new apiRequest function should significantly simplify the code you'll need to write to access the app's API. You shouldn't need to (and shouldn't) write any raw fetches anywhere else.


Part 2: User class

Next, you will implement a User class, a data model that represents a user in our social media app.

Open the API specification, which describes all of the endpoints the API supports. You will probably want to keep this page open as a reference while implementing the class.

Implement the methods in the User class, in js/user.js. The listUsers static method is already written. Since the "List Users" button in part 3 is already hooked up, once you've implemented apiRequest, you should be able to get a list of users by clicking that button. You should not modify the listUsers function. If it isn't working after you have implemented apiRequest, this is a sign that your apiRequest is not implemented as specified above. User's toString is also already written.

Each of the remaining method requires only a few lines of code, thanks in large part to your apiRequest function. Many directly correspond to specific API requests. The comments give a quick summary of the methods; here are the details:

  • loadOrCreate(id): This static method looks up a user. If the user doesn't exist, they are created. Returns a User instance for the found or created user.
  • constructor(data): Creates a new instance, using the data returned by the backend for a user. The keys from the API are set as (public) instance variables, e.g. id, name, etc. You can initialize any private instance variables here as well.
  • save(): Update the user in the backend with the current values of name and avatarURL.
    • As part of this step, implement toJSON(). This special method tells JavaScript how to convert an object to JSON. It returns a (plain) JavaScript object (i.e. a map) with the key/value pairs you want included in the JSON.
    • You don't need to explicitly call toJSON anywhere; if you JSON.stringify the User instance, this method is automatically called for you.
  • getFeed(): Return an array of Post instances that should be shown to the user.
  • makePost(text): Make a post as this user, with the text text.
  • addFollow(id): Have this user follow the user id.
  • deleteFollow(id): Have this user stop follow the user id.

In order to test your User class, you may want to add code to index.js, including e.g. making User available in the console. Alternatively, you may want to consider working on User and App (part 3) in parallel, so you can hook up event listeners for each method as you go.

Notes and tips:


Part 3: App class

Your final task is to incorpate the User class you've written into the app. index.html and css/index.css have been written for you, and your code must work with the HTML provided.

We have started the implementation of the App class in js/app.js. Here are the parts of the app we have written already:

We have also provided a complete implementation of the FollowList class, in js/followlist.js. It is a DOM component that allows the user to add/remove users they want to follow. There is a good amount of code there; while you don't need to understand exactly how it works, it should generally use concepts you are familiar with.

Review the comments and public interface of FollowList to understand how it works. A few high-level notes:

Complete the app by replacing the TODOs. When you are done, the app will behave as follows:

  • When the user enters their user ID into the login input and clicks "Login," their profile is loaded:
    • In the sidebar, their user ID is shown, and their display name and avatar URL are filled in the <input>s.
    • The "Following" panel shows a list of users they are currently following (using FollowList).
    • The feed panel on the left shows the posts by the users they follow, as well as their own posts, from newest to oldest.
  • The user can edit their display name and avatar URL in the sidebar and clicking save (the check mark). These changes are immediately reflected on the feed panel (i.e. they don't have to refresh or log in again).
  • The user can follow and unfollow users using the follow list. If they try to follow a nonexistent user or a user they are already following (including themself), an alert is shown with the error message from the backend. As above, these changes are immediately reflected in the feed.
  • The user can use the form above the feed to make a new post. This post will immediately show up on their feed.

A few notes and tips:

Submitting

We hope this assignment gives you a sense of the kinds of awesome things you're able to build after just a few weeks of learning this stuff. Well done!

When you are finished, please submit your assign3.1 folder (without node_modules) to Paperless. Since it contains subfolders, please make sure to submit the entire folder as-is, not the individual JS files.