/vue-node-code-challenge

Code challenge for a Full-Stack Developer position. It involves use of Vue3 connection to a Node REST API with Swagger documentation that extracts data from the iTunes Search API

Primary LanguageTypeScript

Coding Challenge for a Full-Stack Software Developer role with TypeScript/JavaScript/Vue expertise

Repository where I go through the coding challenge and I keep track of the changes and progress.

Author: Alexandre Gimenez. Attached at the end is the Initial Coding Challenge description document "as-it-is" provided.

I have decided to apply Git flow to the git repository so there are multiple branches for:

  • main,
  • develop
  • features
  • hotfix
  • Skipped: QA and staging are branches that would be useful for real projects but not for a small demo.

Tech Stack Expertise required

  • Back end
    • Node.js and Express.js for RESTful API development and 3rd party API integration (Task 1)
    • Mongoose (Task 2)
  • Front end
    • Vue.js for Single Page Application (SPA) development on the client-side

Technical decissions made

  • The technical decissions taken during the test beared in mind code readability, code reusability, performance/efficiency, scalability, user experiencie and best practices overall.

Roadmap

  • Gather the requirements for Task 1 and Task 2 and plan the subtasks accordingly

Roadmap for Task 1

  • Create the RESTful API
  • Expose the endpoint to retrieve albums from the iTunes Search API
  • Integrate the iTunes Search API into the back-end
  • Learning Vue.js for the very first time and create a boilerplate project
  • Create a Vue.js SPA Front-end displaying the album's names and thumbnails in a grid
  • Create a filtering functionaly using a search box (without API requests)
  • Give written explanations of the technical decissions
  • Learn about Vue.js Unit Testing and Add It to the project

Roadmap for Task 2

  • Give written explanations/justifications for Task 2
  • Refactor the code accordingly

Challenges faced during the development

  1. I never worked with Vue.js before so I have to learn enough quickly to meet the requirements of this technical test. Useful resources:
  1. I wasted multiple hours in the first installation of Vue and integration with Bootstrap.
  2. Swagger API Documentation does not pick the right routes and is not generating. Once that is solved the API Docs will be autogenerated from the code comments

Next Potential Improvements

  • Isolate components to improve their reusability and abstraction
  • Implement Vue Router
  • Implement Vue State Management
  • Implement Multiple Search Filters to search songs and other kind of collections from different Artists
  • Add Continuous Integration
  • Add API Authentication with oAuth2 or Json Web Tokens (JWT)
  • Deploy the API in AWS Lambda using Serverless Framework or through Firebase Functions

Task 2 Analysis:

My refactored code is inside the folder task2 of this repository.

My answers to the questions asked:

Q: What do you think is wrong with the code, if anything?

A: Some imports are missing, there is no error handling at the request level but at the overall function logic either.

Also the logic is making use of "upsert" and "new" parameters. "Upsert" updates or creates a new record in the collection, so I imagine, that we want to retrieve the document created/updated in his latest state (once has been updated/created) and that is why the param. "new" is there. To achieve that we can make use of the parameter "returnNewDocument" rather than "new". Using of callbacks and exec functions when interacting with mongoose might be a bit confusing for developers, but it's correct. Some returns on res.json calls where not properly placed IMHO Standarizing it using async-await helps to a better understandability and readability of the code and his flow.

Q: Can you see any potential problems that could lead to exceptions

A: Some error handling in multiple parts and and that could lead to exceptions.

Q: How would you refactor this code to Make it easier to read + Increase code reusability + Improve the stability of the system + Improve the testability of the code

A: Attached is my code for review of the refactor. This is the summary of the improvements made during the refactor:

  • We must import the superagent module, the User and Shop mongoose models to be able to reference them in the code
  • We can make use of arrow functions in JavaScript modern syntax.
  • "var" can be declared as "let" to restrict the scope to the current function and not above It.
  • The condition: shop.invitations.indexOf to check If an invitation is in the array or not is not being compared to -1, so the 0 will be considered falsy and the -1 truly, which is not the intended behaviour. The refactor suggested is to make use of !shop.invitations.includes(invitationResponse.body.invitationId) to check if the invitationId exists in the array already or not.
  • We can handle errors making use of try-catch statements and .catchError for promises
  • we can make use of .catchError to handle request errors separately
  • Making the inviteUser method asynchronous, we can make use of async-await inside of it, which can be useful to improve code readability when querying the DB and remove the callbacks and execs just by converting the queries to promises with await
  • We can put the return statements in every res.json call

Q: How might you use the latest JavaScript features to refactor the code?

A: I implemented them in the code attached. In summary, we can make use of async await, arrow functions, "let" and not "var" declarations for variables, and other changes documented in the code (available for review as stated).

Other optional improvements that can be made: (opinionable)

  • We can wrap all the errors inside an errorHandler function that builds the same respones object for a better standarization, maintainability and readability
  • We can put the IF conditions in variables with clear names rather than raw compare with status codes. This will improve code readability E.g. isRecordCreated or isInvitedAlready rather than perform a comparison with response status codes
  • We can create the mongoose models and schemas in separated files
  • We can create an abstract class to interact with the database and connect to it
  • We can make use of env. vars (locally with dotenv npm module) to store secrets

Challenge Description:

Full Stack JavaScript Technical Challenge In order to be considered for the position, you must complete the following two tasks. Note:

  1. These tasks should take no longer than a few hours. If you are unsure of anything being asked, feel free to get in touch and ask us questions.
  2. Note that your first task must take into consideration unitTesting with relevant use cases
  3. The 2nd task needs to be backed up with written explanations/justifications
  4. Make sure you upload results to your GitHub account and send us URL to allow us review the results

Task 1: Coding Challenge

Prerequisites

Please note that this will require JavaScript, Express.js and Vue.js knowledge, as well as an understanding of REST APIs. You will also need to have Node.js installed to complete this task.

Steps

  1. Create a repository on Github for the task.
  2. Create an Express.js app that accomplishes the following: a. Connects to the iTunes Search API: https://tinyurl.com/itunes-search-api b. Pulls back a list of albums for a specified artist c. Filters the results so there are no duplicate albums (based on album name) d. Serves the filtered results to the front-end via a route
  3. Create a Vue.js app that accomplishes the following: a. Makes an API request to the above route to fetch the albums b. Displays the albums (as thumbnail & title) in a grid c. Has a live search box to filter (on client-side) the currently displayed albums
  4. Create unit tests as appropriate, with the testing framework of your choice.

But wait…

We are looking for someone who not only completes a project to the specified requirements but also makes use of the newest technologies as well as bringing new ideas to a project. So feel free to add in anything that you would like to share with us during the next stage of the interview process.


Task 2: Analysis Challenge

Prerequisites

Please note that this will require JavaScript, Express.js and Mongoose knowledge, as well as an understanding of REST APIs and best Node.js development practices.

Overview

Below is a Node.js function that a developer has written. It is an express middleware that processes users ' invitations to use private shops.

  • req and res are the express request and response objects
  • superagent is a module that makes http requests and is on npm
  • "User" and "Shop" are mongoose models

Step 1

Analyse the function below and provide answers to the following questions:

  • What do you think is wrong with the code, if anything?
  • Can you see any potential problems that could lead to exceptions
  • How would you refactor this code to:
    • Make it easier to read
    • Increase code reusability
    • Improve the stability of the system
    • Improve the testability of the code
  • How might you use the latest JavaScript features to refactor the code?

Step 2

Provide a sample refactor with changes and improvements you might make. The refactored code does not have to be executable; it will only be used for discussion.

Once both tasks are complete... Commit and push your code from Task 1 and your analysis from Task 2 to your new repository. Then send us a link, we will review and get back to you.

Good luck!

Task 2 Code Snippet (as it is provided)

exports.inviteUser = function(req, res) {
  var invitationBody = req.body;
  var shopId = req.params.shopId;
  var authUrl = "https://url.to.auth.system.com/invitation";

  superagent
    .post(authUrl)
    .send(invitationBody)
    .end(function(err, invitationResponse) {
      if (invitationResponse.status === 201) {
        User.findOneAndUpdate({
          authId: invitationResponse.body.authId
        }, {
          authId: invitationResponse.body.authId,
          email: invitationBody.email
        }, {
          upsert: true,
          new: true
        }, function(err, createdUser) {
          Shop.findById(shopId).exec(function(err, shop) {
            if (err || !shop) {
              return res.status(500).send(err || { message: 'No shop found' });
            }
            if (shop.invitations.indexOf(invitationResponse.body.invitationId)) {
              shop.invitations.push(invitationResponse.body.invitationId);
            }
            if (shop.users.indexOf(createdUser._id) === -1) {
              shop.users.push(createdUser);
            }
            shop.save();
          });
        });
      } else if (invitationResponse.status === 200) {
        res.status(400).json({
          error: true,
          message: 'User already invited to this shop'
        });
        return;
      }
      res.json(invitationResponse);
    });
};