Controllers
Overview
Controllers are the API definition and routing layer.
Job
- Define APIs.
- Routing Requests.
- Methods should not have logic.
- Input validation, like required and data types.
- Delegate business logic to services.
Implementation
Mahaam defines interface and implementation for each controller in one file, for readability.
Mahaam Controllers
- PlanController
- TaskController
- UserController
- AuditController
Sample
IPlanController interface
C#
public interface IPlanController
{
IActionResult Create(PlanIn plan);
IActionResult Update(PlanIn plan);
IActionResult Delete(Guid id);
IActionResult Share(Guid id, string email);
IActionResult Unshare(Guid id, string email);
IActionResult Leave(Guid id);
IActionResult UpdateType(Guid id, string type);
IActionResult ReOrder(string type, int oldIndex, int newIndex);
IActionResult GetOne(Guid planId);
IActionResult GetMany(string? type);
}
Java
public interface PlanController {
Response create(PlanIn plan);
Response update(PlanIn plan);
Response delete(UUID id);
Response share(UUID id, String email);
Response unshare(UUID id, String email);
Response leave(UUID id);
Response updateType(UUID id, String type);
Response reOrder(String type, int oldIndex, int newIndex);
Response getOne(UUID planId);
Response getMany(String type);
}
Go
type PlanHandler interface {
Create(c *ginC)
Update(c *ginC)
Delete(c *ginC)
Share(c *ginC)
Unshare(c *ginC)
Leave(c *ginC)
UpdateType(c *ginC)
ReOrder(c *ginC)
GetOne(c *ginC)
GetMany(c *ginC)
}
TypeScript
export interface PlansController {
create(plan: PlanIn, res: Response): Promise<void>;
update(plan: PlanIn, res: Response): Promise<void>;
delete(id: string, res: Response): Promise<void>;
share(id: string, email: string, res: Response): Promise<void>;
unshare(id: string, email: string, res: Response): Promise<void>;
leave(id: string, res: Response): Promise<void>;
updateType(id: string, type: string, res: Response): Promise<void>;
reOrder(type: string, oldIndex: number, newIndex: number, res: Response): Promise<void>;
getOne(planId: string, res: Response): Promise<void>;
getMany(res: Response, type?: string): Promise<void>;
}
Python
class PlanRouter(Protocol):
def create(self, plan: PlanIn = Body(...)) -> Response: ...
def update(self, plan: PlanIn = Body(...)) -> Response: ...
def delete(self, id: UUID = Path(...)) -> Response: ...
def share(self, id: UUID = Path(...), email: str = Form(...)) -> Response: ...
def unshare(self, id: UUID = Path(...), email: str = Form(...)) -> Response: ...
def leave(self, id: UUID = Path(...)) -> Response: ...
def update_type(self, id: UUID = Path(...), type: str = Form(...)) -> Response: ...
def reorder(self, type: str = Form(...), old_index: int = Form(...), new_index: int = Form(...)) -> Response: ...
def get_one(self, plan_id: UUID = Path(...)) -> Response: ...
def get_many(self, type: str | None = Query(None)) -> Response: ...
PlanController Implementation
C#
[ApiController]
[Route("plans")]
public class PlanController : ControllerBase, IPlanController
{
[HttpGet]
[Route("{planId}")]
public IActionResult GetOne(Guid planId)
{
Rule.Required(planId, "planId");
var plan = App.PlanService.GetOne(planId);
return StatusCode(Http.Ok, plan);
}
}
Java
@ApplicationScoped
@Path("/plans")
@Consumes(Http.JsonMedia)
@Produces(Http.JsonMedia)
class DefaultPlanController implements PlanController {
@GET
@Path("/{planId}")
public Response getOne(@PathParam("planId") UUID planId) {
Rule.required(planId, "planId");
Plan plan = planService.getOne(planId);
return Response.status(Http.OK).entity(Json.toString(plan)).build();
}
}
Go
type planHandler struct {
planService service.PlanService
}
func NewPlanHandler(service service.PlanService) PlanHandler {
return &planHandler{planService: service}
}
func (h *planHandler) GetOne(c *ginC) {
id := req.PathUuid(c, "planId")
plan := h.planService.GetOne(id)
c.JSON(OK, plan)
}
TypeScript
@Controller("plans")
export class PlansController {
constructor(@Inject("PlansService") private readonly plansService: PlansService) {}
@Get(":planId")
async getOne(@Param("planId") planId: string, @Res() res: Response) {
rule.required(planId, "planId");
const plan = await this.plansService.getOne(planId);
res.status(200).json(plan);
}
}
Python
@cbv(router)
class DefaultPlanRouter(metaclass=ProtocolEnforcer, protocol=PlanRouter):
def __init__(self, plan_service: PlanService = Depends(get_plan_service)):
self.plan_service = plan_service
@router.get("/plans/{plan_id}", response_model=Plan)
def get_one(self, plan_id: UUID = Path(...)) -> Response:
Rule.required(plan_id, "planId")
plan = self.plan_service.get_one(plan_id)
return plan
}
Http Methods
- GET: get resource
- POST: Create resource
- PUT: Replace resource
- PATCH: Update resource partially
- DELETE: Delete resource
Http status codes
Mahaam uses status codes as follows:
- 200: OK, success GET, PATCH, PUT
- 201: Created, success POST
- 204: NoContent, success DELETE
- 400: BadRequest, failed in input, GET, POST, PATCH, PUT, DELETE
- 401: Unauthorized, failed in identity, GET, POST, PATCH, PUT, DELETE
- 403: Forbidden, failed in role, GET, POST, PATCH, PUT, DELETE
- 404: NotFound, failed in resource, GET, POST, PATCH, PUT, DELETE
- 409: Conflict, failed in logic, GET, POST, PATCH, PUT, DELETE
- 500: ServerError, failed in server, GET, POST, PATCH, PUT, DELETE