
Internal Portal
A Laravel portal collecting the daily operations of an education provider under one roof: courses and timetables, assignment and exam flows, announcements, a help desk, file management and accounting integration. Archived — active development ended at the close of 2025.
The education operation consisted of work that was connected but ran in separate tools: the timetable in one place, assignments in another, video recordings in a third service, invoicing in a fourth. Each tool worked on its own; what broke were the links between them. When which course a student is enrolled in, whether they can access that course's recording, and the state of their payment are held in three separate places, the only thing joining the three is human memory. The portal's job was not to add a new feature but to establish those links in one place.
Four roles, four different levels of visibility. The student: sees their own timetable, assignments and course recording; cannot reach anyone else's data. The instructor: manages their own courses, exams and student list. The admin: runs enrolment, scheduling, announcements and support requests. The super admin: reaches everything including configuration and user management. The difference between roles is not only in the menu but in data access.
As developer, most of the portal: data model, role and authorisation structure, most of the modules, the external service integrations and the scheduled jobs.
A server-rendered monolith on Laravel 10. A request goes route → controller → Blade view; data access runs through Eloquent. The structure is split into module-based controllers: courses and lessons, students and instructors, exams, announcements, help desk, file management, careers, finance and configuration. External services are gathered in their own controllers — video conferencing, video hosting, file storage, task tracking, accounting, analytics and email. Sanctum handles sessions; API routes live in a file separate from the web routes. Recurring work is collected in a single scheduled job class. On the reporting side, PDF and spreadsheet output is produced directly from the portal.
The portal talks to seven separate external services and each has its own authentication, error format and rate limit. Rather than scattering service calls through business logic, each service was collected at its own entry point; understanding one service's behaviour means looking at one file.
Trade-offThe separation stayed at file level rather than layer level: the calls sit inside controllers with no service layer behind them. So calling the same service from a second place invites copying code, and changing a provider means editing more than one location.
The four roles see different screens, but the screen is not the point: a student must not reach someone else's record and an instructor must not reach a list outside their own course. Authorisation checks were applied at route and controller level rather than simply hiding things in the interface.
Trade-offBecause the check is spread through the application layer, every new endpoint has to re-establish its own; the rule is not enforced at the database level. If someone adding a new module forgets the check, nothing catches it.
The management side regularly needed PDF and spreadsheet output. Leaving that to an external tool would have meant exporting the data and processing it by hand — that is, producing every report manually. Output generation was brought inside the portal.
Trade-offReport generation runs inside the request-response cycle; a large output request directly lengthens the response time. There is no queued generation path.
Laravel was chosen for the sum of what it brings ready-made at this scope: authentication, authorisation, migration management, scheduled jobs, email and a template engine inside one framework. Server-rendered templates made it possible to work without carrying a separate frontend application and an API contract — in work run by a single developer, that halves the number of moving parts. Sanctum gave token-based access for API routes without standing up a heavy authorisation server. If I were doing the same work today I would choose the Next.js side; the decision at the time was made in the context of the team and the codebase I inherited.
Authentication runs on Laravel's own layer, with session and API access kept separate. Authorisation checks are applied at route and controller level and the difference between the four roles is visible in data access. External service keys are held in environment variables. The portal is a closed system: it has no public face and enrolment is run by the administration. No real screenshot is published in this case study — every screen carries student and staff data.
Because it is a closed system there is no stored, date-stamped measurement output and no score is written here. Structurally, pages are produced on the server and sent as full pages; no heavy application layer is carried on the client. The cost of that is that every interaction is a full page reload. Report generation also runs inside the request cycle, so response time grows directly with large outputs.
The most expensive gap in this portal was the absence of automated tests, and you pay for that in a system with seven integrations: when a provider changes its response format, only a user notices the error. Today I would at least put the external service calls behind a service layer and test that layer with fake responses — as long as the calls sit inside controllers, there is no testable boundary. The second point is authorisation: the checks stayed scattered through the application layer and every new endpoint had to re-establish its own. I saw a harsher version of the same pattern in Taxilos, where the rule was repeated in 45 places and a single predicate error opened all the gates. The third is reporting: I would move output generation out of the request cycle and into a queue; today a user asking for a large report waits for that request to finish.
Tell me what you want to build; I'll tell you up front how long it takes and where to start.