{"id":16976974,"url":"https://github.com/rithinch/.netcore-web-api-example","last_synced_at":"2025-08-03T09:06:25.482Z","repository":{"id":111641078,"uuid":"372078104","full_name":"rithinch/.netcore-web-api-example","owner":"rithinch","description":"👾 Simple REST API implementation using .NET 5,  clean architecture principles, and Docker containers.","archived":false,"fork":false,"pushed_at":"2024-11-14T07:01:47.000Z","size":104,"stargazers_count":0,"open_issues_count":2,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-28T10:54:11.100Z","etag":null,"topics":["docker","dotnet-core","dotnet5","refit","rest-api"],"latest_commit_sha":null,"homepage":"","language":"C#","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/rithinch.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":"2021-05-29T22:11:53.000Z","updated_at":"2021-06-02T09:20:46.000Z","dependencies_parsed_at":"2024-11-28T07:16:06.904Z","dependency_job_id":null,"html_url":"https://github.com/rithinch/.netcore-web-api-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rithinch%2F.netcore-web-api-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rithinch%2F.netcore-web-api-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rithinch%2F.netcore-web-api-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rithinch%2F.netcore-web-api-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rithinch","download_url":"https://codeload.github.com/rithinch/.netcore-web-api-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248975299,"owners_count":21192199,"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":["docker","dotnet-core","dotnet5","refit","rest-api"],"created_at":"2024-10-14T01:27:46.228Z","updated_at":"2025-04-14T22:51:21.355Z","avatar_url":"https://github.com/rithinch.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\t\u003cimg src=\"https://rithinpersonalstorage.blob.core.windows.net/images/pokedex.gif\" width=\"200\" height=\"150\"\u003e\n\t\u003cbr\u003e\n    \u003cbr\u003e\n    \u003ch1\u003ePokedex Web API\u003c/h1\u003e\n    \u003cp\u003e\n\t\t\u003cb\u003eSimple .NET 5 implementation of a service that provides pokemon information. 👾\u003c/b\u003e\n\t\u003c/p\u003e\n    \u003cbr\u003e\n    \u003ca href=\"https://github.com/rithinch/.netcore-web-api-example/actions/workflows/dotnet.yml\"\u003e\u003cimg src=\"https://github.com/rithinch/.netcore-web-api-example/actions/workflows/dotnet.yml/badge.svg\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://codecov.io/gh/rithinch/.netcore-web-api-example\"\u003e\n  \u003cimg src=\"https://codecov.io/gh/rithinch/.netcore-web-api-example/branch/main/graph/badge.svg?token=LTRBOVNDHY\"/\u003e\n\u003c/a\u003e\n    \u003ca href=\"https://www.codacy.com/gh/rithinch/.netcore-web-api-example/dashboard?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=rithinch/.netcore-web-api-example\u0026amp;utm_campaign=Badge_Grade\"\u003e\u003cimg src=\"https://app.codacy.com/project/badge/Grade/3b850ea67d164757af491bc1f9ba7b46\"/\u003e\u003c/a\u003e\n    \u003cbr\u003e\n    \u003cbr\u003e\n    \u003cbr\u003e\n\u003c/div\u003e\n\nThis repo contains an example of a REST API implemented in .NET Core using clean architecture principles and containerized with Docker. The service was developed keeping a pokemon domain in mind, but the principles used can easily be applied to design .NET Core based web APIs for any domain. For simplicity, we're using [PokeAPI](https://pokeapi.co/) to retrive pokemon information instead of a custom database.  \n\n\n**Tech Stack:** [.NET 5](https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-5.0), [Docker](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/?view=aspnetcore-5.0), [Refit](https://github.com/reactiveui/refit), [AutoMapper](https://github.com/AutoMapper/AutoMapper), [Moq](https://github.com/Moq/moq4/wiki/Quickstart)\n\n## Prerequisites ✔️\n\nTo run the app locally, you'd need to have **either** of the following installed on your machine:\n\n1. [Docker Desktop](https://docs.docker.com/desktop/#download-and-install) - can build a docker image and spin up a container running the app locally. \n2. [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) - can use the dotnet CLI or visual studio to build and run the app.\n\n## Running Locally 💻\n\nWe first need to get a local copy of this repository either by cloning or downloading the zip files.\n\nIf you want to run the app without using command line, you can open the `Pokedex.sln` file and directly run in Visual Studio.\n\nOnce you have a localy copy, open up your terminal/command prompt at the **root** of this repo.\n\n### Dotnet CLI\n\nThe following commands will launch the app on http://localhost:5000\n\n```bash\n\u003e cd Pokedex/Pokedex.API\n\u003e dotnet run\n```\n\n### Docker 🐳\n\nThe following commands will build the image and run the app in a docker container.\n\n**Docker Compose**\n```bash\n\u003e docker compose up\n```\n**Individual Dockerfile**\n\n```bash\n\u003e cd Pokedex\n\u003e docker build -t pokedex-api .\n\u003e docker run -it --rm -p 5000:80 pokedex-api\n```\n\nThis will run on: http://localhost:5000\n\nYou can now test the endpoints using any HTTP client tool like cURL, Postman, httpie etc.\n\nExample using [httpie](https://httpie.io/):\n\n```bash\n\u003e http http://localhost:5000/pokemon/pikachu\n\u003e http http://localhost:5000/pokemon/translated/pikachu\n```\n\n\n## Endpoints 🔎\n\nOnce you run the app, you should be able access the public routes below:\n\nFeature | Type | Route | Access\n------------ | ------------- | ------------- | -------------\nGet Pokemon Details | GET | http://localhost:5000/pokemon/:name | Public\nGet Pokemon with translated description | GET | http://localhost:5000/pokemon/translated/:name | Public\n\n\n**Example Response Model**:\n\n```json\n{\n    \"description\": \"Their electricity could build and cause lightning storms.\",\n    \"habitat\": \"forest\",\n    \"isLegendary\": false,\n    \"name\": \"pikachu\"\n}\n```\n\nSwagger documentation is also configured. More information on the API endpoints and responses can be accessed at this endpoint: \n\n```url\nhttp://localhost:5000/swagger\n```\n\n![Swagger Documentation UI](https://rithinpersonalstorage.blob.core.windows.net/images/swagger.png)\n\n## Architecture 📐\n\nUsed [Clean Architecture](https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures#clean-architecture) approach to organize code into 3 projects.\n\n**Pokedex.Application.Core**\n\nThis project contains only core domain entities (POCO), business logic and required interface abstractions. It is the inner most layer and **should not** have any dependency on outside layers.\n\n- _Entities/PokemonEntity.cs_ - This is a POCO class that is used to represent information of a single pokemon.\n- _Services/PokemonService.cs_ - This uses the client interfaces to get the data and applies the business logic of using the correct translation etc.\n- _Clients/IPokeAPIClient.cs_ - Interface exposing a method to fetch pokemon species from an external source. We don't need to worry about the implementation details of this interface at this layer, it's responsibility of Infrastructure layer. The external source now happens to be PokeAPI, as long as this interface is maintained, it can be easily changed to another source without having to change the PokemonService.\n\n\nWith dependencies injected, it's simple to mock them in unit tests and test only the core business logic. 😎\n\nFor this example, the client interfaces and external models are defined in this layer. But for production, I would have created a much more generic interface and models such as 'IPokemonRepository' instead of directly calling it `IPokeAPIClient`. The infrastructure layer, would implement the interface and can convert the external models into domain specific models. \n\n**Pokedex.Infrastructure**\n\nThis layer contains implementations of any interfaces defined in Application.Core that require talking to external systems or data sources. Typically, it should have an implementation of PokeAPIClient and FunTranslationsClient i.e. code that makes http requests and serializes/deserializes requests and responses into external models. \n\nTo avoid writing http request making code in this example, `Refit` library was used. It allows us to dynamically create implementations of API Clients based on interfaces.\n\nThe following code in `DependencyInjection.cs` class of this project, is used to inject the implementation of the client at runtime in Startup.cs of the API layer.\n\n```csharp\nservices\n    .AddRefitClient\u003cIPokeAPIClient\u003e()\n    .ConfigureHttpClient(c =\u003e c.BaseAddress = new Uri(\"https://pokeapi.co/api/v2\"));\n```\n\nOnly a single instance of the client is created and used throughout the application wherever `IPokeAPIClient` dependency is passed in the constructor.\n\n**Pokedex.API**\n\nThis is the outer most 'presentation' layer. It includes the following:\n\n- _PokemonController.cs_ - Defines the API routes and is the entry point. All Application.Core dependencies are injected via constructor and only interfaces are used throughout. The response models are mapped from the business entities and sent back in the response.\n\n- _PokemonModel.cs_ - Domain entities might have properties/information that we don't want to expose to the client, hence it they're converted to this model and sent back as the response.\n\n- _Startup.cs_ - Configures the application and dependency injection.\n\nAutoMapper has been used to simplify creation of API models from domain entities.\n\nThere are multiple deployment options for this application such as hosting in a container independently (AWS ECS or Azure Container Service), adding to an existing Kubernetes cluster etc. We can re-use the inner layers and convert the entire presentation layer into a new serverless API project, if needed. The architecture is flexible.\n\n\n## Unit Tests 🧪\n\n**Pokedex.Application.Core.Test.Unit** includes unit tests for the core business logic.\n\nThe tests can be run either through Visual Studio or dotnet CLI.\n\n```bash\n\u003e cd Pokedex\n\u003e dotnet test\n```\n\n## Production Ready? 🚀\n\n... Not yet. \n\nThe following are a few things to consider adding before taking this to production. \n\n**External Services Response Caching**\n\nWe can cache the responses we recieve from external services such as PokeAPI and FunTranslations. This can improve performance of the app and could reduce the number of requests we make. If the third party service charges us on the number of requests, then caching could result in cost savings. It depends on the types of requests we make, some can't be cached. In this case, the response includes pokemon information and translation which are unlikely to change often for given input.\n\nThe cache can be in-memory of the app or using an internal service like Reddis.\n\n**Request Model Validation**\n\nThe input request models don't have any validation at the moment. It works with both id (int) and name (string). Having some validation of the input models before passing the data into inner layers or external systems is a good safety measure.\n\n**PokeAPI Response Model Deserialization**\n\nCurrently some values like language, habitat are stored as string. In code to verify we're doing something like `.Language == \"en\"` and `.Habitat == \"cave\"`. It would be an improvement to see if we can map these strings to Enum when we deserialize, so we don't have to hardcode such strings in code.\n\n**Making it Resilient**\n\nWhen making calls to anything outside of our application such as external thrid party services or data sources, our application has to be fault tolerant. Dependent services can be down, fail, rate limited etc. We need to handle our logic to either fallback to a default value, have a retry policy with a circuit breaker pattern or design internal systems with asynchronous communication that use message queues. \n\nCurrently, if PokeAPI or FunTranslation service fails due to rate-limit or any reason, then we fallback to use a standard description or default value. We can log such failures and fallback to see exceptions in a monitoring dashboard.\n\nPerhaps for this case, a simple retry policy with default fallback and logging should be good enough. 🔱\n\nMore information: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/\n\n**Monitoring**\n\nHaving a monitoring solution in place is extremely important for any deployed production service. What kind of monitoring platform we use largely depends on the overall system architecture and where it's deployed to. \n\nIf deployed to azure app service or container instances, we can use Azure Application Insights. If we're deploying to microservices in a cluster, we can use Prometheus.\n\nIt's important we are able to monitor and detect any performance issues, failures, exceptions, memory usage etc. \n\n**Service Health Endpoints**\n\nAdding on to monitoring above, having service health endpoints are also important. These could simple operation to test connectivity of data source or external service. If returns 200 OK, means all infrastructure is working fine. For example, we can use tools like WatchDog, to be able to monitor health of microservices.\n\n**Authentication and Authorization**\n\nAt the moment, anyone can make requests to this application. Adding an auth mechanism will restrict who can make the requests. The implementation for this largely depends on the overall system architecture. If there is an API gateway, then that can handle the auth and perhaps pass JWTs to internal services which the microservices can validate with a middleware in place. 🔒 \n\n**Additional things to consider:**\n\n- Versioning the API\n- Rate Limiting\n- Setup CI/CD Pipeline based on deployment infrastructure. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frithinch%2F.netcore-web-api-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frithinch%2F.netcore-web-api-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frithinch%2F.netcore-web-api-example/lists"}