Giter Club home page Giter Club logo

socialdeck-api's Introduction

Assignment 1 - Agile Software Practice

pipeline status coverage report

Name: Tsvetoslav Dimov

Overview

SocialDeck is currently an API that enables users to manage their posts (Create, Read, Update, Delete, Share). It uses GraphQL, instead of REST because of the following reasons:

  • Declarative data fetching: there's no need to call multiple endpoints to access various data, like with traditional REST approach. Instead, you specify the exact data you need and GraphQL gives you exactly what you asked for.
  • Improved performance: GraphQL improves performance by helping avoid round trips to the server and reducing payload size. If one want to take advantage of GraphQL, they don't even have to scrape their existing REST API, as GraphQL can be built on top of REST.
  • GraphQL was developed by Facebook and it's currently open-sourced. It's quickly gaining popularity. A few of its the most notable users are Airbnb, GitHub, The New York Times, Coursera.

API endpoints

GraphQL is typically served over HTTP via a single endpoint which expresses the full set of capabilities of the service. This is in contrast to REST APIs which expose a suite of URLs each of which expose a single resource.

The GraphQL endpoint for my API is '/graphql'.

GraphQL comes with an in-browser API explorer. If you navigate to /graphql endpoint in your browser, you will see it in action. There you can check all existing queries and mutations if you click on the button DOCS, located on the right side of the page. Underneath it you will see another button SCHEMA, which will show you the full API schema. It’s also possible to write queries and mutations directly on the left tab and check their execution by clicking the PLAY icon in the middle of the screen.

If you'd like to see a video demo of the Explorer and API calls, CLICK HERE.

  • Queries

    • users: [User!]! - Gets a list of all users.
    • findUserById(_id: String!): User! - Finds a user by ID.
    • me: User! - Gets currently authenticated user.
    • posts: [Post!]! - Gets a list of all user posts.
    • findPostById(_id: String!): Post! - Finds a post by ID.
  • Mutations

    • signUp(email: EmailAddress!, password: String!, firstName: String!, lastName: String!): String! - Takes in the specified arguments, creates a new user and signs them in.
    • logIn(email: EmailAddress!, password: String!): String! - Takes in the specified arguments and logs the user in.
    • logOut: String! - Logs out currently authenticated user, if present. Returns a request status message.
    • deleteUserById(_id: String!): User! - Deletes a user by its ID. Returns the deleted document.
    • deleteAllUsers: String! - Deletes all users. Returns a request status message.
    • createPost(message: String, links: [LinkInput!]): Post! - Takes in the specified arguments and creates a new post. Returns the created document.
    • sharePost(postID: String!): Post! - Adds the ID of currently authenticated user to the shares array of the specified posts. Returns the shared post document.
    • updatePost(postID: String!, message: String!, links: [LinkInput!]): Post! - Takes in the specified arguments to update an existing post. Returns the updated document.
    • deletePostById(_id: String!): Post! - Deletes a post by its ID. Returns the deleted document.
    • deleteAllPosts: String! - Deletes all posts. Returns a request status message.

Data model

Simply put, the database stores users and their posts.

The password field is an encrypted value of their original password, created by a package called bcrypt. It is then use to verify if hashed value of the original password matches the provided password argument to mutations like logIn() and signUp().

The email field should be an email address of valid format. If it is not, the API will complain and not perform the creation of User object.

Posts and Users are stored in separate databases. Using GraphQL I was able to make a connection between a Users and their Posts. I use the User's _id to fetch their Posts collection from the database and populate it as the value of posts array in the User objects.

Each Post object has a field creatorID, which references a value from the User database.

The createdTime and updatedTime fields are generated by a package called 'moment'. It gets the current date and time and stores it in UTC format, keeping the local time. Initially updateTime would null, but once a Post gets updated, this value changes to the exact time when the update happened.

The links field stores array of URLs and they are checked if they are of valid URL format before being saved to the database.

The shares field stores an array of Users _id values. Initially it is null, until the Post object gets shared by a User.

[
    {
        "_id": "5dbff437f482e01d03fecd4b",
        "email": "[email protected]",
        "password": "$2b$10$u8JvPqJ3v08S.s9zL6LOy.su65KlcQr3dmYUqhv0rzUXYqtpgV7O2",
        "firstName": "Test",
        "lastName": "Johnson",
        "posts": [
            {
                "_id": "5dbff437f482e01d03fecd4d",
                "creatorID": "5dbff437f482e01d03fecd4b",
                "createdTime": "2019-11-04T09:49:43.000Z",
                "message": "Test message by user 1...",
                "updatedTime": "2019-11-04T10:50:01.000Z",
                "links": [
                    "https://github.com/Urigo/graphql-scalars/",
                    "https://moodle.wit.ie/"
                ],
                "shares": [
                    "5dbff437f482e01d03fecd4c"
                ]
            }
        ]
    },
    {
        "_id": "5dbff437f482e01d03fecd4c",
        "email": "[email protected]",
        "password": "$2b$10$u8JvPqJ3v08S.s9zL6LOy.su65KlcQr3dmYUqhv0rzUXYqtpgV7O2",
        "firstName": "Thomas",
        "lastName": "Farrell",
        "posts": [
            {
                "_id": "5dbff437f482e01d03fecd4e",
                "creatorID": "5dbff437f482e01d03fecd4c",
                "createdTime": "2019-11-04T09:49:43.000Z",
                "message": "Test message by user 2...",
                "updatedTime": null,
                "links": [
                    "https://mongoosejs.com/docs/api/",
                    "https://developer.github.com/"
                ],
                "shares": []
            }
        ]
    }
]

Sample Test execution.

  SocialDeck
      GraphQL API
        • Authentication
          ◦ signUp()
            ✓ should be able to sign up with a valid email (132ms)
            ✓ should return error if email is not unique
            ✓ should return error if the user is already logged in (81ms)
          ◦ logIn()
            ✓ should be able to log in with valid credentials (70ms)
            ✓ should return error if the user is already logged in (79ms)
            ✓ should return error if user's email is not in the db
            ✓ should return error if user's password does not match the db record (73ms)
          ◦ logOut()
            ✓ should be able to log out an authenticated user (74ms)
            ✓ should return error if the user has not logged in
        • Queries
          ◦ me()
            ✓ should be able to return currently authenticated user (85ms)
            ✓ should return error if the user has not logged in
          ◦ users()
            ✓ should return all users from the db (83ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if there are no users in the db (79ms)
          ◦ findUserById()
            ✓ should fetch existing user (83ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the user does not exist (81ms)
          ◦ posts()
            ✓ should return all posts from the db (83ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if there are no posts in the db (81ms)
          ◦ findPostById()
            ✓ should fetch existing post (80ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the post does not exist (77ms)
        • Mutations
          ◦ deleteUserById()
            ✓ should delete an existing user (79ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the user does not exist (78ms)
          ◦ deleteAllUsers()
            ✓ should delete all existing users (78ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if 'users' database is empty (81ms)
          ◦ createPost()
            ✓ should create a new post (81ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if 'links' argument contains a badly formatted URL (79ms)
          ◦ deletePostById()
            ✓ should delete an existing post (82ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the post does not exist (86ms)
          ◦ deleteAllPosts()
            ✓ should delete all existing posts (83ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if 'posts' database is empty (83ms)
          ◦ sharePost()
            ✓ should share an existing post (90ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the post does not exist (93ms)
          ◦ updatePost()
            ✓ should update an existing post (86ms)
            ✓ should return error if the user has not logged in
            ✓ should return error if the post does not exist (84ms)
      Other tests
        ✓ should get index page
  
  
    45 passing (3s)

Extra features

GraphQL API

The assignment requirements stated that we must create a REST API and write tests for it. Having heard of GraphQL before, I knew about its advantages over REST and was curious to try it out for the first time. After a discussion with my lecturer, we agreed that GraphQL would suit this project with one condition - to provide proper documentation about its usage and pros/cons.

GraphQL comes with built-in scalar types (Int, Float, String, Boolean and ID). While this covers most of the use cases, often the developer would need custom type like Date, Email, URL and so on. I used graphql-scalars package, that provides a collection of scalars that can be used out of the box. I used scalar types Email, Date and URL to support validation in my GraphQL API.

Authentication

By default Express requests are sequential and no request can be linked to each other. Which means that users cannot be identified unless using JWT token and Authorization header, or some other mechanism. That's why I decided to implement express-session. By using it every user of the API will be assigned a unique session, which allowed me to store the user state across the requests to the API. The sessions are persisted on MongoDB and I've set them to expire 1 hour after creation. All the API queries and mutations require the user to authenticate before they get access to the data. However, there are two GraphQL mutations that don't require authentication. They are logIn() and signUp(). After successful authentication, the user doesn't need to provide credentials as their session is stored in a cookie, accessible in all the requests.

When a user registers, their password gets stored on the database. Storing passwords as plain text is a bad practice. For that reason I used bcrypt to store a hash of the original user's password. In future logIn() requests I compare the hashed password from the database with the password that the user provides on log in and catch relevant errors, which then I send back to the user.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.