In a nutshell a simple many-to-many relationship modeling with some primitive CRUD and UI
We have a web application where our admins can see/review/manage users.
Our customer-success department is missing functionality to group users based on their skills.
- As CS-manager, I want to attach skills to users, so I can easily filter the list of users by skill.
- I choose a user
- I attach skill "javascript" to the user
- I filter the list of users by skill "javascript"
- I see result 1 row with the user from 2nd step
The challenge defines a single acceptance criteria and a general background. Because this challenge is about primitive CRUD and a basic UI, I've added CRUD for skills and kept the UI functional.
- Several Skills can be linked to several users at once.
- Any number of unique skills can be created. If the user tries to create a duplicate skill, an error will be shown and the user needs to change the name.
- Deleting a skill will also remove any assignments.
- It's not possible to rename a skill after creation.
- Any number of skills can be assigned to a user.
- The list of users can be filter by a skill.
- Execute
yarn
in the root folder of repository to install all dependencies - Execute
yarn start
in the root folder of repository to start both frontend and backend in parallel - Execute
yarn test
in the root folder of repository to execute all tests in sequence
Remarks
react-scripts
doesn't support node >=18 out of the box. Please use node 16. Previous versions of node are still supported but even node 16 will be eol in 6 months.
- I have opted for a node.js backend implementation because I am not familiar enough with the used python frameworks
- Both backend and frontend are written in Typescript and have a 100% test coverage. Some parts are only covered indirectly, but I think that's sufficient for this challenge.
- As package manager, I've used
yarn
because backend and frontend are organized as mono repository. - A minimal eslint and prettier config has been used.
- Frontend:
swr
is used for rest requests to the backend. It provides a nice developer flow, similar to apollo GraphQL hooks. It also caches requests.- All components are either placed in
components
(shared stuff) or underpages/**/*
. If a component is atleast used by more than page, it's put incompoments
, otherwise it's kept in the page folder. - Tests are written using
Testing Library
which uses the Behavioural Testing approach. Long story short, it encourages to focus on the user flow, not the technical implementation. That's also why I haven't included any e2e tests. While Behavioural Tests cannot replace e2e Tests, they can provide a similar confidence with less effort (test duration). The test setup is done intest/*
- During testing, all network requests are captured by
msw
at the "network level". That means thatswr
will work under "real conditions" but receive fake data as arranged in the tests. That approach reduces the number of mocks: less mocks, more stable tests. - Styling: Usually I would prefer to use third party frameworks or an internal collection of predefined components. For this challenge I decided to keep everything "nice" and simple. User Experience hasn't been a priority either. For example, not all errors will be shown, only a subset of the pages actually render error messages.
- Filtering of the users is done client side. Server-side filtering could have been done instead but in the current setup, that would be overkill.
- Backend:
hapi.js
is used as web server framework. Routes are put intoroutes/*
and each handler is in a separate file. Database source is passed down during setup.- Error handling is basic, yet simple. There is a very simple global
errorHandler
to catch sql constraint errors. The error handling should be moved to the individual route instead as soon as complexity of the routes increases. - Tests are written as integration tests. Hapi allows to "inject" requests. That is used to validate the handlers are working as expected.
- An in-memory database is used. Ideally this is moved out of the package and is provided as part of the development infrastructure (for example as docker container). Currently, the database will be dropped on each start of the backend.
typeorm
is used as orm. Because of the simple data schema, I opted for a basic orm setup, including eager loading of entities.
Backend and Frontend can be started individually by executing the start
script of packages. Same for the tests.
- Use a third party library for UI components (for example Material UI)
- Share & generate typings by using swagger documentation.
- Replace the in-memory database with a dockerized database (for development only). That decouples database & backend and enables future optimizations (layering of infrastructure).
- Replace
react-scripts
withvite
(it's faster and easier to use) - Replace
jest
withvitest
. Idearly bothvite
andvitest
are used. We had really good experience with both. - To improve UX, improve error handling by showing all errors and provide exact error messages.
- Instead of showing cached data to increase responsiveness, use placeholders or spinners to indicate that something is happening.
- Add dockerfile to build and ship both backend and frontend