Take home test for Node.js developers.
This challenge has been designed to measure your knowledge of Node.js, Express, Typescript and various technologies, like monorepos, databases and testing. For your exercise, you will be enhancing this API which serves as the backend for the Pleo app. Whenever a user of the app navigates to the expenses view, it calls this API to collect the list of expenses for that user.
Your objective is to write this new route to fetch the list of expenses for a given user. Right now that domain is empty, so you'll have to build everything from scratch- but you can look over at the user domain for inspiration. Please make sure that the endpoint scales adequately and supports paging, sorting and filtering. Additionally, we would also like you to write some tests for your route.
Finally, as a bonus objective, try to improve any aspect of this API. It could be to add more TS types, better security, tests, add features, graphql support, etc.
Fork this repo with your solution. Ideally, we'd like to see your progression through commits, and don't forget to update the README.md to explain your thought process.
Please let us know how long the challenge takes you. We're not looking for how speedy or lengthy you are. It's just really to give us a clearer idea of what you've produced in the time you decided to take. Feel free to go as big or as small as you want.
Make sure that you have a modern version of yarn
that supports workspaces (>= 1.0
), then run:
yarn
You will also need to install Postgres, create a challenge
database and load the sql file dump.sql
:
psql challenge < dump.sql
To enable logs, use the standard NODE_DEBUG
flag with the value DEBUG
NODE_DEBUG=DEBUG yarn start
Make sure that you have a modern version of yarn
that supports workspaces, then run:
yarn test
The command above will run the following test suites sequentially:
Test suite | Run command | Description |
---|---|---|
Unit | yarn test:unit |
Simple unit tests. |
Mid-level | yarn test:mid-level |
Small integration tests that integration of small components together. |
Acceptances | yarn test:acceptance |
Large integration tests, system tests, end-to-end tests. |
Happy hacking 😁!
Summary: Clone repo, setting up, install software, create DB, looking around and running initial code.
- Fix
TypeError: res.status is not a function
error by adding nextFunction - Need to remove straight SQL queries from db-user.ts (
"SELECT * FROM USERS"
) by creating models -> typeorm - Investigate https support
- Add more checks to security.ts
- API routes don't seem optimal, something like
/v1/users/:id/details
would be better than/user/v1/get-user-details/?userId=d
. - I understand the
/user
route is implemented as its own package within the monorepo architecture, so it is good to have versioning for each package (in case they are managed by different teams, for example). - Something like
/user/v1/:id/details
might be better. - Potential improvements to the db schemas,
expenses.status
column is varchar(100). It could be a foreign key reference to astatus
table that contains all possible statuses. Same forusers.company_name
column andexpenses.user_id
Summary: Creating expense domain
- Setting up basic expense domain as a starting point. Major refactoring to come :)
- I might need to consolidate both formatter.ts into 1 file under
packages/utils
- Field validation -> allow negative numbers for
expenses.amounts_in_cents
? - Need to add API documentation -> text file containing a summary of the available routes. Something like :
GET /expense/v1/expense -> return all expense
)
Summary : Setting up ORM for database operations
- Set up typeorm for expense domain and adding 2 basic queries
- Got rid of the 'get-*' part of the routes. It is unnecessary, since we know what type of action to do based on the HTTP query (GET, POST, PUT, DELETE, etc)
- Cleanup of unused or unnecessary code and rearranged the folders for the expense domain into a MVC-like structure
Summary: Work on user domain and other fix and improvments
- Migrated user domain to typeorm. I reused the formatter display the correct columns and capitalize. Another option
would be to only select these columns in the
find()
statement + capitalize the result directly with a.then()
or something - I decided to change the
expense/v1/expenses/:user_id
route toexpense/v1/expenses/user/:user_id
in order to make it clearer that we are getting the users expenses. I also addedexpense/v1/expenses/:expense_id
- Added type definitions on the controllers so that the TS gods stay happy
Summary: Created and tested (manually) paging, sorting and filtering.
- Added paging, filtering and sorting in the package utils + testing
- Need to write tests for my routes that covers all cases
Summary: Created tests
- Added unit tests for parse-url-params and acceptance test for the expense and user routes
- Added API documentation
The Challenge was definitely fun to do. Most of my experience in node was with npm
, but I found that yarn
was as easy to use. The "recorded time" for the different sessions are not super accurate, as I took some time to look
up a bunch of things out of curiosity, like : "What are the Pros and Cons of using a monorepo ?" or comparing ORMs.
I noted a couple of things that I would add to improve this app, if I had the time:
- Integrate authentication and authorization with something like Passport (https://www.npmjs.com/package/passport)
in order to restrict the access to the api to the users that are logged in, but also to restrain access to routes
that are more for "admins" like
/user/v1/users
. More security and validation around the routes and what can be submitted would also be a good thing. - Add routes that would make it possible to do CRUD operations on the DB through POST, PUT, DELETE, etc.
- Foreign keys in the DB schema (i.e.: foreign key from
expenses.user_id
touser.id
) and other improvements like creating a seperate table to hold all the possible expenses status as text and refer to thestatus.id
in the expenses table rather than having a varcharexpenses.status
. - Add more acceptance/integration tests to cover every single route and unit test to cover all controllers and functions within the utils. Gotta keep that code coverage up.
Thank you for the opportunity, and I look forward to speaking with you again :)