{"id":17933938,"url":"https://github.com/papooch/example-crud-backend","last_synced_at":"2025-04-03T11:24:06.696Z","repository":{"id":209415557,"uuid":"723859382","full_name":"Papooch/example-crud-backend","owner":"Papooch","description":null,"archived":false,"fork":false,"pushed_at":"2023-11-28T04:16:03.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-09T00:42:50.616Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Papooch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-26T23:09:19.000Z","updated_at":"2023-11-27T07:39:58.000Z","dependencies_parsed_at":null,"dependency_job_id":"b76c4af6-b553-4d8b-b37d-e6c7431c024d","html_url":"https://github.com/Papooch/example-crud-backend","commit_stats":null,"previous_names":["papooch/example-crud-backend"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papooch%2Fexample-crud-backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papooch%2Fexample-crud-backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papooch%2Fexample-crud-backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Papooch%2Fexample-crud-backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Papooch","download_url":"https://codeload.github.com/Papooch/example-crud-backend/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246990515,"owners_count":20865486,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-28T21:42:27.932Z","updated_at":"2025-04-03T11:24:06.671Z","avatar_url":"https://github.com/Papooch.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Example CRUD backend\n\nExample CRUD backend built with `express`, `ts-rest` and `prisma`.\n\n## Requirements\n\n-   The task is to build a simple CRUD backend for managing \"tasks\" on \"projects\"\n-   Each task must be assigned to a project and can be in 3 states - _Open_, _In Progress_ and _Done_.\n-   Each task can also be assigned from 0 to 100 tags\n-   The properties of a project are: `title`, `description` and dates for `createdAt` and `updatedAt`\n-   The properties for a task are: `projectId`, `description`, `state`, `tags` `createdAt` and `updatedAt`\n-   The API needs to be able to CRUD projects and tasks, while also being able to filter tasks by state and tags with pagination.\n-   The API should be documented\n-   The database should be Postgres and the backend should be written in ExpressJS.\n\n## Decisions made\n\n### Language\n\nTypeScript, there's no argument here.\n\n### API\n\nThe requirement was to document the API, so I chose to use `ts-rest` to generate the API documentation and the API itself. This way, the API is always up to date with the documentation and is end-to-end type safe.\n\nIf there was no requirement for Express, I would have used the NestJS framework, which offers a lot of features out of the box, including API documentation and dependency injection.\n\n### ORM\n\nI chose `prisma` because it's the most modern and type safe ORM for TypeScript. The main advantage is it's schema-first design, which also allows to automatic generation of database migrations.\n\nFor more complex queries, I would have used a query builder like `knex` or `kysely`. The disadvantage of those is that they don't include a migration engine, which is essential for ease of use.\n\n### Architecture/Design patterns\n\nI chose grouping by feature, while using a kind of service/repository architecture within each feature (although the lines are arguably quite blurry given the lack of business logic). This way, the code is easier to navigate and the dependencies are easier to manage.\n\nI also implemented dependency injection (with manual resolution of dependencies in `main.ts`), which allows for easier testing and better separation of concerns.\n\nYou can also notice a lot of \"duplication\" when it comes to entity types - there is one for the Database, the API and the application. This is intentional as is allows for independent evolution of each layer, while only requiring a simple mapping between the layers.\n\nThe main challenge was finding a way to manage the tags, as that was the only \"complex\" part of the application. I chose to use a separate table for the tags and joined them via a join table to form a many-to-many relationship. The other would be to use an array column to store just the tags, but that would make it harder to query and filter for them without proper indexing. This is all abstracted away in the API layer though, so the user only interacts with an array of strings.\n\nThe constraint of maximum 100 tags is also enforced in the API layer by not allowing more than 100 tags to be added to a task - updating a task's tags requires sending the entire array of tags - this might be a bit inconvenient, but it's simple and given it's only 100 tags, the performance implications are minimal. The only issue is deletion of \"orphaned\" tags, which is not addressed here. This could be done by a scheduled job or by a trigger in the database.\n\n### Testing\n\nThere are no tests yet (there is really no business logic for test), but I would have used `jest` for both unit and integration tests. Having designed the application with IoC in mind, it would be easy to mock the dependencies and test the application in isolation.\n\n### Not implemented\n\nFor the lack of time, there is no `Delete` logic at all. I would have implemented a soft delete, where the entity is not deleted from the database, but marked as deleted (using a new `deletedAt` column). This way, the entity can be restored if needed.\n\n### Possible improvements\n\nThe error handling could be improved. Right now, there is some basic separation between Application and API errors with a simple translation between them using a simple error handler. More exception cases could be handled with some recovery logic.\n\nThere is no authentication or authorization. I would have used `passport` for authentication and `casl` for authorization. Or more likely a managed solution like `Auth0` or `Keycloak`.\n\nNo logging is implemented. I would have used `pino` for logging. As for traces and telemetry, I would have used `opentelemety`, which plays nicely with the `pino` logger.\n\nThe API documentation is served using the ugly `swagger-ui-express`. I would have used my favourite documentation tool - [RapiDoc](https://rapidocweb.com/), which makes a better use of screen real estate, while also being more visually appealing, but needs a bit more configuration.\n\n### Time spent\n\nI spent around 5 hours on the project, including the initialization, experimenting with `ts-rest` and documentation, with the actual implementation taking around 3 hours.\n\n# Development \u0026 Deployment\n\n## Dependencies\n\nThe project requires `node` and `yarn` to be installed.\n\nTo run the development database you need `docker` and `docker-compose`.\n\n## Install\n\nThe project is managed with `yarn`. To install the dependencies run:\n\n```bash\nyarn install\n```\n\n## Develop\n\nCopy the `.env.example` file to `.env` and fill the variables.\n\nRun the development database with:\n\n```bash\ndocker compose up -d\n```\n\nRun database migrations with `prisma`:\n\n```bash\nyarn prisma migrate dev\n```\n\nRun the development server with:\n\n```bash\nyarn dev\n```\n\nGo to [`localhost:3000/api-doc`](localhost:3000/api-doc) to see the API interactive documentation.\n\n## Test\n\nThere are no tests yet.\n\n## Build\n\nBuild the project with:\n\n```bash\nyarn build\n```\n\nRun the built project with:\n\n```bash\nnode dist/main.js\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapooch%2Fexample-crud-backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapooch%2Fexample-crud-backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapooch%2Fexample-crud-backend/lists"}