Development Guide
This guide provides detailed instructions for setting up your development environment and working with the Clean Architecture Docker .NET Angular starter project.
Prerequisites
Before you begin, ensure you have the following installed:
- Docker Desktop (latest version)
- Git
- Visual Studio Code (recommended) or another IDE
- .NET 9 SDK (for local development without Docker)
- Node.js (LTS version, for local development without Docker)
Getting the Code
Clone the repository:
git clone https://github.com/nitin27may/clean-architecture-docker-dotnet-angular.git
cd clean-architecture-docker-dotnet-angular
Development Options
You have two primary options for development:
- Docker-based development (recommended)
- Local development (separate setup for frontend and backend)
Docker-based Development
Environment Setup
-
Create your environment file:
cp .env.example .env # (PowerShell) Copy-Item .env.example .env
-
Edit the
.env
file to configure environment variables as needed. Key settings include:- PostgreSQL connection details
- JWT secret and configuration
- SMTP settings for email functionality
Starting the Development Environment
Run the development containers:
docker-compose up
This will start the following services:
- PostgreSQL database
- .NET API with hot reload
- Angular frontend with hot reload
- NGINX as a reverse proxy
Development URLs
- Frontend: http://localhost:4200
- API: http://localhost:5000/swagger
- API through NGINX: http://localhost/api
Rebuilding Containers
If you make changes to Dockerfiles or docker-compose.yml:
docker-compose up --build
Stopping the Environment
docker-compose down
To remove volumes (database data will be lost):
docker-compose down -v
Local Development (Alternative)
Backend (.NET API)
-
Navigate to the backend directory:
cd backend/src
-
Restore dependencies:
dotnet restore
-
Update the PostgreSQL connection in
appsettings.Development.json
-
Run the API:
dotnet run --project Contact.Api
The API will be available at
https://localhost:7224
andhttp://localhost:5217
Frontend (Angular)
-
Navigate to the frontend directory:
cd frontend
-
Install dependencies:
npm install
-
Update the proxy configuration in
proxy.conf.json
to point to your local API -
Start the development server:
npm run serve
The frontend will be available at
http://localhost:4200
Database Access
Using Docker Tools
Access the PostgreSQL database using:
docker exec -it postgres_db psql -U postgres -d contacts
Using External Tools
You can also connect using external tools like pgAdmin, DBeaver, or JetBrains DataGrip with these connection details:
- Host: localhost
- Port: 5432
- Username: contacts_admin (or as configured in your .env file)
- Password: (from your .env file)
- Database: contacts
Development Workflow
Making Backend Changes
- Modify code in the appropriate layer:
- Domain: For entity definitions and core business logic
- Application: For use cases, services, and orchestration
- Infrastructure: For external integrations and data access
- API: For endpoints and controllers
-
If using Docker, changes will be automatically applied with hot reload.
- Follow the Clean Architecture principles:
- Domain layer has no external dependencies
- Application layer depends only on Domain
- Infrastructure implements interfaces defined in Domain/Application
- API layer is the entry point but delegates to Application services
Making Frontend Changes
- Follow these best practices:
- Use standalone components
- Use signals for state management
- Use the inject() function for dependency injection
- Integrate Material components with TailwindCSS utilities
- Implement permission checks for UI elements
- Structure new features following the established pattern:
/feature /your-feature /components your-feature.routes.ts your-feature.service.ts your-feature.interface.ts
- For state management, follow the signal pattern:
// Component state loading = signal<boolean>(false); items = signal<YourType[]>([]); // Computed values totalItems = computed(() => this.items().length); // Updating state fetchItems() { this.loading.set(true); this.service.getItems().subscribe({ next: (data) => { this.items.set(data); this.loading.set(false); }, error: (err) => { this.loading.set(false); this.notificationService.error('Failed to load items'); } }); }
Testing
Backend Tests
Run .NET tests:
cd backend/src
dotnet test
Frontend Tests
Run Angular tests:
cd frontend
npm test
Debugging
Debugging Backend
-
With Docker:
Configure VS Code for Docker debugging by adding this to
.vscode/launch.json
:{ "name": ".NET Core Docker Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickRemoteProcess}", "pipeTransport": { "pipeProgram": "docker", "pipeArgs": ["exec", "-i", "api"], "debuggerPath": "/usr/local/bin/vsdbg", "pipeCwd": "${workspaceFolder}", "quoteArgs": false }, "sourceFileMap": { "/app": "${workspaceFolder}/backend/src" } }
-
Without Docker:
- Use standard .NET debugging in your IDE
- Set breakpoints and run with F5 in VS Code or Visual Studio
Debugging Frontend
- With Docker:
- Access the application at http://localhost:4200
- Use browser developer tools to debug
- Source maps are enabled for easier debugging
- Without Docker:
- Use standard Angular debugging with browser dev tools
- For VS Code, use the “Launch Chrome” launch configuration
Working with Permissions
The system uses a role-based permission model:
- Backend Permission Definition:
- Permissions are stored in the database as Page-Operation combinations
- Controllers use the
[AuthorizePermission("PageName.Operation")]
attribute
- Frontend Permission Checks:
- Routes are protected with the
PermissionGuard
- UI elements use the
*hasPermission
directive - Example:
*hasPermission="{ page: 'Contacts', operation: 'Create' }"
- Routes are protected with the
- Adding New Permissions:
- Add permission in the database seed script
- Assign permission to roles in
RolePermissions
table - Use consistent naming:
{EntityName}.{Operation}
Common Tasks
Adding a New Entity
- Create Entity in Domain Layer:
public class YourEntity : BaseEntity { public required string Name { get; set; } // Other properties... }
- Create Repository Interface:
public interface IYourEntityRepository : IGenericRepository<YourEntity> { // Additional methods if needed }
- Implement Repository:
public class YourEntityRepository : GenericRepository<YourEntity>, IYourEntityRepository { public YourEntityRepository(IDapperHelper dapperHelper) : base(dapperHelper, "YourEntities") { } }
- Create DTOs:
public class CreateYourEntityDto { /* properties */ } public class UpdateYourEntityDto { /* properties */ } public class YourEntityResponseDto { /* properties */ }
- Create Service Interface and Implementation:
public interface IYourEntityService : IGenericService<YourEntity, YourEntityResponseDto, CreateYourEntityDto, UpdateYourEntityDto> { } public class YourEntityService : GenericService<YourEntity, YourEntityResponseDto, CreateYourEntityDto, UpdateYourEntityDto>, IYourEntityService { public YourEntityService(IGenericRepository<YourEntity> repository, IMapper mapper, IUnitOfWork unitOfWork) : base(repository, mapper, unitOfWork) { } }
- Register in Dependency Injection:
// In ApplicationServiceCollectionExtensions.cs services.AddScoped<IYourEntityService, YourEntityService>(); // In InfrastructureServiceCollectionExtensions.cs services.AddScoped<IYourEntityRepository, YourEntityRepository>();
- Create API Controller:
[Route("api/[controller]")] [ApiController] [Authorize] public class YourEntityController : ControllerBase { private readonly IYourEntityService _service; public YourEntityController(IYourEntityService service) { _service = service; } [HttpGet] [AuthorizePermission("YourEntity.Read")] public async Task<IActionResult> GetAll() { var entities = await _service.FindAll(); return Ok(entities); } // Other actions... }
- Create Angular Service and Components:
// service @Injectable({ providedIn: 'root' }) export class YourEntityService { private http = inject(HttpClient); getAll() { return this.http.get<YourEntity[]>('/api/YourEntity'); } // Other methods... } // component @Component({ standalone: true, // other metadata }) export class YourEntityListComponent { private service = inject(YourEntityService); entities = signal<YourEntity[]>([]); // Component logic... }
Building for Production
Backend
cd backend/src
dotnet publish -c Release
Frontend
cd frontend
npm run build
Using Docker
docker-compose -f docker-compose.yml up --build
Troubleshooting
Common Issues
- PostgreSQL Connection Issues:
- Check connection string in
.env
file - Ensure PostgreSQL container is running
- Check connection string in
- Frontend API Connection Issues:
- Verify API URL in environment settings
- Check proxy configuration
- Permission Errors:
- Verify user has the required role and permissions
- Check permission attribute on controller action
- Hot Reload Not Working:
- Ensure Docker volume mappings are correct
- Check for file watching issues
Getting Help
If you encounter issues:
- Check the documentation
- Review existing GitHub issues
- Open a new issue with detailed information about your problem
Additional Resources
- See Backend Documentation for detailed backend information
- See Frontend Documentation for detailed frontend information
- See Clean Architecture Series for architectural deep dives