concept: user
header: User
states
username: User -> one String
password: User -> one Password
dateJoined: User -> one Date
concept: chirp (aka post)
header: Post
states
author: Post -> one User
dateCreated: Post -> one Date
content: Post -> one Content
dateModified: Post -> one Date
concept: chirper (aka friend)
header: Friend
states
user: Friend -> one User
friends: Friend -> set User
concept: nests (aka circles)
header: Nest[Item]
states
name: Nest -> one Name
creator: Nest -> one User
members: Nest -> set User
posts: Nest -> set Item
concept: group times
header: Time[Object]
states
creator: Time -> one User
group: Time -> one Object
startTime: Time -> one Hour
endTime: Time -> one Hour
app Fritter
concepts
User
Post
Friend
Nest[Post.Post]
Times[Nest.Nest]
One of the tradeoffs I struggled with was either implementing a lot of middleware, but exposing more endpoints with my collections, or creating a code base robust enough to handle some common errors and exposing less endpoints with my collections. I ended up choosing the later, since I decided to focus on making my endpoints only expose the most necessary functionality needed to fully implement my operating principles. For example, I prevent users from being able to GET
all freets, all users, all times, and all nests. Instead, I check that a user trying to get all of a creator’s nests can only be the creator themselves (i.e. only the creator of the nests can see all of their nests). I also struggled where to place which middleware, like checking that the creator of a nest is a valid user. To prioritize modularity, I placed any user checks in the user middleware, nest checks in the nest middleware, and so forth.
In addition, in terms on implementation, I realized there was some tension between my code modularity and actual synchronizations. For example, I debated whether to sync the deletion of users and posts to my nests and friends. I decided to make a cohesive app, it was important for me to sync the deletion and also sync creations: when a user is created, a friend object to the FriendCollection
is also immediately made. These synchronizations are also an example of how, rather than exposing the POST
endpoints to users, I created a code base that would automatically handle creation/deletion to prevent duplicates, input errors, malicious attacks, etc.
In terms of my routes, specifically for friends, I had to decide what routes make the most sense in terms of RESTful design. I ended up choosing to create POST
and DELETE
routes to represent adding and removing a friend, but technically, in my implementation, these are just updates to a user’s friend object.
Lastly, some of the tradeoffs I noticed while working on were deciding what to delegate what concepts to actually implement in my backend versus what concepts to delegate to my frontend. I ended up deciding not to implement my profile, feed, or filtering concepts since they can represented as the data from calls to the endpoints of user, nest, post, times, and friend.