{"id":21585222,"url":"https://github.com/felipeoriani/nodejs-typescript-express-rest-api","last_synced_at":"2025-05-04T08:23:37.670Z","repository":{"id":232311643,"uuid":"782606025","full_name":"felipeoriani/nodejs-typescript-express-rest-api","owner":"felipeoriani","description":"Sample NodeJs, Typescript and Express REST / Graphql API service used to manage and handle Tasks by User. Deploy is build on the top of AWS ECS, AWS ECR and AWS RDS with Github Actions workflows.","archived":false,"fork":false,"pushed_at":"2024-07-24T23:26:26.000Z","size":441,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-30T21:33:17.637Z","etag":null,"topics":["apollo","apollo-server","apollo-server-express","clean-architecture","clean-code","docker","express","express-middleware","expressjs","graphql","node-test-runner","nodejs","postgresql","prisma","prisma-client","prisma-orm","rest-api","typescript","unit-test"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/felipeoriani.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2024-04-05T16:34:20.000Z","updated_at":"2024-12-28T13:19:16.000Z","dependencies_parsed_at":"2024-04-18T20:44:29.787Z","dependency_job_id":"f4e9f3b8-ea0a-437a-a79a-4a56119c5f9f","html_url":"https://github.com/felipeoriani/nodejs-typescript-express-rest-api","commit_stats":null,"previous_names":["felipeoriani/task-service","felipeoriani/nodejs-typescript-express-rest-api"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipeoriani%2Fnodejs-typescript-express-rest-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipeoriani%2Fnodejs-typescript-express-rest-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipeoriani%2Fnodejs-typescript-express-rest-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/felipeoriani%2Fnodejs-typescript-express-rest-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/felipeoriani","download_url":"https://codeload.github.com/felipeoriani/nodejs-typescript-express-rest-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252135627,"owners_count":21699945,"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":["apollo","apollo-server","apollo-server-express","clean-architecture","clean-code","docker","express","express-middleware","expressjs","graphql","node-test-runner","nodejs","postgresql","prisma","prisma-client","prisma-orm","rest-api","typescript","unit-test"],"created_at":"2024-11-24T15:09:33.172Z","updated_at":"2025-05-03T02:54:28.326Z","avatar_url":"https://github.com/felipeoriani.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Task Service\n\nTask Service is a sample application used to handle Tasks by User. The goal here is to show in a simple project how I enjoy desigin and implementing the solutions I have creates after a few yeras of experience working with NodeJs. I am always open mind to change it since there is no silver bullet from my perspective.\n\nThe base structure of a `Task` is:\n\n```typescript\n{\n  id: string\n  title: string\n  description: string\n  status: 'todo' | 'inProgress' | 'done' | 'archived'\n  createdAt: Date\n  userId: string\n}\n```\n\nThe REST API / GraphQL API is protected by user authentication using a `bearer` token (JWT). Before consuming it, you must first authenticate using the `POST /api/v1/auth` endpoint passing the user credentials and a token will be generated. Use it on the `authorization` http request header attribute. Use POSTMAN collection available on the docs folder.\n\n⚠️ **Important**: There is 2 types of user. A `super` user that can handle all the tasks and a `non-super` user which can only handle its own tasks. You may get some `401` or `403` errors if you try to acccess wrong IDs.\n\nHere is a structure of a `User`:\n\n```typescript\n{\n  id: string\n  name: string\n  username: string\n  password: string\n  email: string\n  createdAt: Date\n  super: boolean\n}\n```\n\n## 💻 Stack\n\nThe following list represents the main stack and its dependencies:\n\n- NodeJs 20\n- Node Test Runner\n- Typescript 5.x\n- Express\n- PostgreSQL database\n- Prisma ORM\n- Apollo Server (`apollo-server-express`)\n- eslint\n- jsonwebtoken\n- pino (logging)\n\n## 🥇 Architecture\n\nThe design was made on the top of `Clean Architecture`, where there are well-separated layers for `Domain` which holds all the abstractions for the domain of the application (Tasks and Users), `Infrastructure` (I/O bound operations) and `UseCases` (business rules). The components of each layer depends on the abstractions of the domain which allows us to inject any derived instance for the abstractions and mock at unit testing level.\n\n![Postman collection](docs/project-structure.png)\n\n## ☁️ Scalability\n\nTo emulate the scalability, you can run it on your machine:\n\n```\ndocker-compose -f docker-compose-test.yml up -d\n```\n\nIt will up database, two instances of the container app and a `nginx` as a load balancer which will distribute the requests across the containers emulating a situation where we have more than a single instance running for the application. The image for the container app is ready and published on docker hub, you can check it here: https://hub.docker.com/r/felipeoriani/tasks-api-nodejs-typescript\n\n```mermaid\nflowchart TD\n    G(Client) -.loadBalancer:4000.-\u003e LB(Nginx Load Balancer)\n    subgraph Backend App\n        LB -.api01:3000.-\u003e API1(NodeJs API - instance 01)\n        LB -.api02:3000.-\u003e API2(NodeJs API - instance 02)\n        API1 -.-\u003e Db[(PostgreSql Database)]\n        API2 -.-\u003e Db[(PostgreSql Database)]\n    end\n```\n\nYou also can see how the image was build at `Dockerfile`.\n\nBut from this repository you will need to run:\n\n```\nyarn prisma:reset\n```\n\nTo migrate and seed the database. Then you can load the postman collection available on the `docs` folder and use it.\n\nOnce it is up, the endpoint to consume the api via load balancer is: `http://localhost:4000`.\n\nFinally when you are done, just destroy the environment:\n\n```\ndocker compose -f docker-compose-test.yml down\n```\n\n## ✔️ Tests\n\nIt uses the native Node Test Runner as tooling for testing. The tests covers the use cases layer mocks are created using objects and injected on the useCases, specially the `TaskUseCases`.\n\n![Postman collection](docs/test-results.png)\n\n## 📊 CI/CD\n\nThere is a initial workflow on the `./github/workflows` folder that run a few steps to check the project source code including lint, typescript, build (transpilation process of typescript) and tests.\n\nYou can see the workflow results at `Actions` tab here: https://github.com/felipeoriani/task-service/actions\n\n## 📓 Improvements\n\nThere are space for improvements in this project considering coding, architecture, deployment, automation:\n\n- Implement integration tests to cover the infrastructure layer and api layer;\n- Custom error messages for schema validators, maybe considering globalization;\n- Implement hashing strategy for user password (security issue);\n- Standardize the http response messages using the Problem Details;\n- GraphQL configurations, it was my first time dealing with it, I would investigate how to implement the `Mutation` and maybe consider it just a GraphQL API instead of a Mix of REST and GraphQL;\n- Improve the test coverage, currently using the `c8` package since node test runner is not able yet (hope in node 22 it will);\n- Improve the route configuration on the API level with the express framework;\n- Configure CORS properly;\n- Configure Rate limiting on the API / Cloud service infrastructure;\n- Move environment variables to AWS Secrets Manager and adapt the application to read from there;\n- Deploy at AWS ECS (Fargate - serverless) and configure the Task Definitions properly to run and scale the container;\n- Use AWS RDS (Aurora) to run the PostgreSQL database;\n- Use AWS Elasticache (Redis) to implement some caching strategy;\n- Use IaC tool (Terraform or AWS Cloud Formation) to provision the infrastructure and automate it for dev/staging/prod;\n- Improve the CI/CD workflows with Github Actions to deploy it into a Cloud Service (currently just doing the basics with CI).\n\n## Endpoints\n\n![Postman collection](docs/postman-collection.png)\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003eMethod\u003c/th\u003e\n        \u003cth\u003eEndpoint\u003c/th\u003e\n        \u003cth\u003eDescription\u003c/th\u003e\n        \u003cth\u003eStatus\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eGET\u003c/td\u003e\n        \u003ctd\u003e/\u003c/td\u003e\n        \u003ctd\u003eBase endpoint\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eGET\u003c/td\u003e\n        \u003ctd\u003e/health\u003c/td\u003e\n        \u003ctd\u003eHealthcheck for the API returning which can be configured on a container orchestration tool.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003cth colspan=4\u003eGraphQL\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003ePOST\u003c/td\u003e\n        \u003ctd\u003e/api/v1/graphql\u003c/td\u003e\n        \u003ctd\u003eEndpoint to get tasks using Apollo Server. You can read all the tasks, a single task by the id or all the tasks filtered by status.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003cbr /\u003e401 - Unauthorized\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003cth colspan=4\u003eUser\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003ePOST\u003c/td\u003e\n        \u003ctd\u003e/api/v1/auth\u003c/td\u003e\n        \u003ctd\u003eAuthenticate a user using credentials and database users.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003cbr /\u003e404 - Not Found\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003cth colspan=4\u003eTasks\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eGET\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks\u003c/td\u003e\n        \u003ctd\u003eReturns all the tasks for the authenticated user. In case it's a super user request, return all the tasks.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eGET\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks/:id\u003c/td\u003e\n        \u003ctd\u003eReturn a specific task by the given id and authenticated user. In case it's a super user, ignore the authenticated user.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003cbr /\u003e404 - Not Found\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003ePOST\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks\u003c/td\u003e\n        \u003ctd\u003eSave a new task for the authenticated user considering a valid request body.\u003c/td\u003e\n        \u003ctd\u003e201 - Created\u003cbr /\u003e400 - Bad Request\u003cbr /\u003e422 - Unprocessable Entity\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003ePATCH\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks/:id\u003c/td\u003e\n        \u003ctd\u003ePartially update an existing task for the authenticated user considering a valid request body. Only the title and description.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003cbr /\u003e400 - Bad Request\u003cbr /\u003e422 - Unprocessable Entity\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003ePATCH\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks/:id/status\u003c/td\u003e\n        \u003ctd\u003eChange the status of a given task for the authenticated user considering a valid request body.\u003cbr/\u003e A task can be moved to any status but once it is in the Archive status, it can't be moved anymore.\u003c/td\u003e\n        \u003ctd\u003e200 - OK\u003cbr /\u003e400 - Bad Request\u003cbr /\u003e422 - Unprocessable Entity\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eDELETE\u003c/td\u003e\n        \u003ctd\u003e/api/v1/tasks/:id\u003c/td\u003e\n        \u003ctd\u003eDelete a task by the given id for the authenticated user.\u003cbr/\u003e It's a hard delete, the data will be lost once it succeed.\u003c/td\u003e\n        \u003ctd\u003e204 - No Content\u003cbr /\u003e400 - Bad Request\u003cbr /\u003e404 - Not Found\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelipeoriani%2Fnodejs-typescript-express-rest-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffelipeoriani%2Fnodejs-typescript-express-rest-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffelipeoriani%2Fnodejs-typescript-express-rest-api/lists"}