Syed Ahmer ShahSyed Ahmer ShahSyed Ahmer ShahSyed Ahmer ShahSyed Ahmer Shah — home
  1. …
  2. Engineering Logs
  3. The Engineering Logs
  4. AIT: Building a University Admissions Portal From Scratch
Part 19 of 21

AIT: Building a University Admissions Portal From Scratch

One admissions cycle, from a student's first visit to their first class, and everything that has to go right in between

Written by

Syed Ahmer Shah

Software Engineer

Oct 30, 20268 min read1,717 words—reading now▼
The Ahmer Institute of Technology home page beside the admin applications queue, under the title AIT case study
Read this articlevoice
Markdown

If you've ever applied to a university in Pakistan, you know the routine. You fill in a long form, scan your CNIC and your marksheets, pay a fee challan at a bank, upload a photo of the stamped receipt, and then wait. Somewhere on the other side, an admissions officer opens your file, checks every document, and decides.

In the summer of 2026 I spent a month as a Full Stack Developer intern at XCL Technologies in Hyderabad, working on exactly that kind of system: a university admission and examination portal. Registration, document uploads, challans, exam slips, and a Super Admin panel to run it all. I learned more in that month than in a semester, mostly because real users were going to touch it.

When the internship ended I wanted to know if I could build the whole thing on my own, end to end, and then take it further than we had time for. That became Ahmer Institute of Technology, or AIT. The institute is fictional. The code, the security and the tests are real, and the source is on GitHub.

The AIT home page: Learn engineering by building things that work, with a photo of the campus gate and a Spring 2027 intake card
The AIT home page: Learn engineering by building things that work, with a photo of the campus gate and a Spring 2027 intake card

#What it does

AIT follows one admissions cycle from start to finish.

An applicant registers, fills in the application, ranks up to three programs and uploads their documents. An admissions reviewer opens the file, looks at each document and approves or rejects it with a note. Approval gives the new student an ID like 26BSSE007 and a one-time password, and enrols them in their first-semester subjects. The student signs in, is made to pick their own password, and then sees their classes, timetable, attendance, marks and fee challans.

Teachers take attendance and enter marks, but only for the subjects an admin assigned to them. A Head of Department sees their own department. The admin runs fees, exam slips, receipts, appeals, the timetable and the public website copy, and can hand parts of that work to sub-admins.

So it's really six things sharing one database: a public site with 14 pages, an applicant portal, a student portal, a teacher portal, a staff portal and an admin console. The database has 31 tables and 2 reporting views.

The admissions page: five steps to apply and the Spring 2027 schedule, from applications opening on 1 November to classes starting on 15 February
The admissions page: five steps to apply and the Spring 2027 schedule, from applications opening on 1 November to classes starting on 15 February

#Why plain PHP

At XCL we used Laravel. For AIT I chose plain PHP and MySQL with no framework, on purpose. A framework hands you sessions, CSRF protection and safe queries for free, and I wanted to understand each of those by writing them myself. It runs on a normal XAMPP install, which is what most students in my class have on their laptops, and it also runs in Docker.

The cost is that nothing protects you by default. Every form, every query and every file download had to be made safe by hand, and that shaped most of the project.

#Uploads were the hardest part

An admissions portal asks strangers to send you files. That's the riskiest thing a website can do. A file called photo.jpg can be a PHP script with a fake name. A PDF can carry JavaScript. An image can hide extra data after its last pixel.

Every upload in AIT goes through one pipeline, in this order:

  1. Only JPG, PNG, WebP and PDF are allowed.
  2. The first bytes of the file have to match its extension. A script renamed to .jpg fails here.
  3. Images have a 5 MB limit and PDFs 10 MB.
  4. The file is sent to ClamAV, an antivirus scanner. If the scanner is down, the upload is refused instead of being let through.
  5. Images are decoded and saved again as fresh WebP files, which throws away hidden data and metadata. PDFs are rebuilt with Ghostscript, and any PDF with scripts, attachments, forms or encryption is rejected.
  6. The file gets a random 128-bit name and is stored outside the website's public folder.
  7. It can only be downloaded through one page that checks who you are. Students get their own files. Admins get them only if their role allows it.
The admin review window for an applicant: personal details, academic marks, program choice and the list of submitted documents with View and Download buttons
The admin review window for an applicant: personal details, academic marks, program choice and the list of submitted documents with View and Download buttons

#Two clicks at the same moment

Some bugs only happen when two things happen at once. Two admins open the same application and both press "Approve". An applicant double-clicks "Submit" on a slow connection. Without care you get two student IDs for one person, or two applications in a cycle that should only allow one.

AIT handles this in two places. The database refuses duplicates outright: one application per student per cycle, one challan per application, unique CNICs and emails. And every action that two people could race locks the row first (SELECT ... FOR UPDATE), so the second click waits, sees the work is done, and stops. Student IDs come from the database's own primary keys, so two approvals can never hand out the same number.

#Who can do what

The admin console has sub-admins with nine separate permissions. One sub-admin might only handle admissions, another only teachers. Each page checks the permission on every request, and sub-admin access can expire on a date.

Teachers are limited to their own classes. That one sounds obvious, but my first version only checked that you were a teacher, not that the student was in your subject. The end-to-end tests caught it, which I'll come back to.

The timetable checks for clashes too. If a new slot overlaps another class with the same teacher or in the same room, the admin is told which subject it clashes with and has to pick another time.

The admin timetable for Software Engineering: day, time, subject, teacher, room and section for each slot
The admin timetable for Software Engineering: day, time, subject, teacher, room and section for each slot

Also readThe Discount That Knew You Were Hesitating: Rule-Based Personalisation in an Online Pharmacy
The Last Mango Problem: Race Conditions in a Real Marketplace

#The rest of the security

The list is long, so here are the parts I'd want to know about as a user:

  • Passwords are hashed with Argon2id. After three wrong attempts you get a captcha, after five the account locks for a while.
  • Every form carries a secret token so another website can't submit it on your behalf, and the server also rejects posts that the browser marks as cross-site.
  • A strict Content Security Policy means that even if someone sneaks HTML into a field, the browser won't run their script.
  • Every form has a rate limit per account and per address, plus a hidden honeypot field and a timing check that bots usually fail.
  • Files like .env, .git, SQL dumps and the backend folder are never served to the web.

#Making it fast and findable

The public site is what parents and students actually see first, so it gets the same care as the portals. Every page has its own title and description, canonical URL, social sharing card, breadcrumbs and structured data that tells search engines this is a university with programs and an FAQ. The sitemap is generated, not written by hand.

For speed there are several cache layers: OPcache for PHP itself, Redis for sessions, rate limits and data, APCu as a fast local layer, and a full-page cache for visitors who aren't signed in. Images are responsive WebP, and text is compressed with Brotli.

The whole stack runs with one command in Docker: Apache and PHP 8.5, MySQL 8, Redis 7 and ClamAV. The image is published to GitHub Packages.

An enrolled student's dashboard: welcome message, student ID, attendance eligibility, profile details and the semester fee challans
An enrolled student's dashboard: welcome message, student ID, attendance eligibility, profile details and the semester fee challans

#Testing with fourteen people

This is the part I'm proudest of. I wrote 58 Playwright tests that play out a whole admissions cycle with fourteen made-up people: applicants who make every mistake, a parent using the contact form, admins with different permissions, a teacher, a Head of Department, a security guard, and an attacker named Eve who tries everything she can think of. There are also 23 backend tests, including one that uploads a real antivirus test file and checks ClamAV catches it.

Those tests found bugs I had no idea existed. Applicants were sent to a page that didn't exist after signing in. Account lockouts never held, because MySQL and PHP disagreed about the time zone. Teachers could mark students outside their class. About one temporary password in 500 broke my own password rules. I wrote about the people and the bugs they found in a separate, less technical post: I invented 14 people to break my university website.

GitHub runs both suites against the full Docker stack on every push.

#What I got wrong

I trusted the happy path. For months I tested by clicking through as one well-behaved applicant. Every serious bug lived somewhere that person never went: a second admin, a teacher from another department, a wrong password, a slow double-click.

I mixed design systems. The public site, the student portal and the admin console were built at different times and still look like three slightly different products. A real university would want one look. I made them consistent in light and dark mode, but I never unified them.

"Coverage" is a slippery word. Every page, route and portal action is exercised by a test. Line-by-line code coverage isn't measured, so I can't claim every line has been run. If I did it again I'd set that up on day one.

#By the numbers

  • 6 connected parts: a public site and five portals
  • 31 database tables and 2 reporting views
  • 58 end-to-end tests with 14 personas, and 23 backend tests
  • 15 bugs found and fixed in the v2.0.0 round, most of them by the tests
  • 7 steps every uploaded file has to pass

#Where it stands

AIT is finished. Version 2.0.0 is the final release and the repository is now archived, which means it's read-only and kept as it is. It did what I wanted it to do: it took a month of internship work and turned it into something I understand from the first line to the last.

The source, the screenshots and the Docker image are on GitHub.

Find me across the web.

Same person, every platform

Work & code

  • ASPortfolioahmershah.dev
  • LinkedInin/syedahmershah
  • GitHub@ahmershahdev
  • AWSAWS Builder Center@syedahmershah
  • Crunchbasesyed-ahmer-shah
  • LinkedIn · Companycompany/syedahmershah

Writing

  • Dev.tosyedahmershah
  • Medium@syedahmershah
  • Hashnode@syedahmershah
  • Substack@syedahmershah
  • HackerNoonu/syedahmershah
  • CLCoderLegionuser/syedahmershah

Reviews & listings

  • Google Knowledge PanelSyed Ahmer Shah
  • Google Business Profileg.page
  • ClClutch
  • TpTrustpilot
  • DRDesignRush
  • TBTechBehemoths

Social

  • YouTube@ahmershahdev
  • Instagram@ahmershahdev
  • TikTok@ahmershahdev
  • Facebookahmershahdev
  • X@ahmershahdev
#ait#casestudy#php#mysql#websecurity#docker#playwright#engineeringlogs
ShareXLinkedInRedditWhatsApp

← Previous in series

The Day the Money Moves

Next in series →

I Invented 14 People to Break My University Website

On this page

  • What it does
  • Why plain PHP
  • Uploads were the hardest part
  • Two clicks at the same moment
  • Who can do what
  • The rest of the security
  • Making it fast and findable
  • Testing with fourteen people
  • What I got wrong
  • By the numbers
  • Where it stands

The Engineering Logs

  1. 01The Edge Latency Lie: Solving Global Consistency
  2. 02Top 10 AI Tools Every Frontend Developer Should Know (2026 Guide)
  3. 03Change One Number. Take Everything.
  4. 04How Databases Work: From Tables to Query Execution
  5. 05The New Era of Software Engineering
  6. 06The Art of Object-Oriented Programming
  7. 07GleanGrid: Building a Pre-Order Marketplace for Farmers Markets
  8. 08The Last Mango Problem: Race Conditions in a Real Marketplace
  9. 09BookMyMovie: Building a Cinema Ticketing Platform
  10. 10One Seat, Two Buyers: Race Conditions, IDOR and Keys in a Cinema Booking System
  11. 11Zovita+: Building an Online Pharmacy That Adapts to Each Shopper
  12. 12The Discount That Knew You Were Hesitating: Rule-Based Personalisation in an Online Pharmacy
  13. 13RAG Retrieval Strategies on a 50k-Document Dataset: BM25 vs Dense vs Hybrid
  14. 14Inside the Database Stack
  15. 15HolidayOps: Building a Working Days API for 250 Countries
  16. 16The Weekend Is Not a Constant
  17. 17MarketClock: A Trading Calendar That Shows Its Work
  18. 17Cybersecurity Trends: Protecting Data in the Digital Age
  19. 18The Day the Money Moves
  20. 19AIT: Building a University Admissions Portal From Scratch
  21. 20I Invented 14 People to Break My University Website

Up next

Keep reading.

All 59 articles→
  1. 01

    I Invented 14 People to Break My University Website

    A plain-language story about testing a university admissions website with fourteen imaginary people, from a careless applicant to a would-be hacker, and the real bugs they found that I never would have.

    Oct 30, 20267 minThe Engineering Logs

  2. 02

    Zovita+: Building an Online Pharmacy That Adapts to Each Shopper

    How I rebuilt Zovita+, an online pharmacy with 1,186 real medicines, a 3D symptom body map, personal offers, drug-interaction warnings, refill reminders, pharmacist-reviewed prescriptions, Urdu, and card or cash payments, on Laravel, React and MySQL: stack, data model, checkout locking, payments, admin, security, SEO, testing, what broke and what I'd change.

    Oct 2, 202622 minThe Engineering Logs

  3. 03

    One Seat, Two Buyers: Race Conditions, IDOR and Keys in a Cinema Booking System

    How BookMyMovie makes sure one cinema seat is never sold twice, a guessed booking number shows nothing, and a leaked or missing key can't quietly break tickets: row locks, lock order, a NULL-tolerant unique key, status compare-and-set, idempotency, IDOR scoping, HMAC tickets, captcha step-up, and the bugs that still got through.

    Sep 29, 202611 minThe Engineering Logs

  4. 04

    BookMyMovie: Building a Cinema Ticketing Platform

    How I built BookMyMovie on my own, a Laravel, React and MySQL cinema booking platform with live seat maps, trailers, QR e-tickets that work offline, online payments and an Urdu interface: stack, architecture, data model, design, SEO, security, what broke and what I would change.

    Sep 29, 202617 minThe Engineering Logs

  5. 05

    MarketClock: A Trading Calendar That Shows Its Work

    How I built MarketClock on my own: a Flask and Next.js app that tells traders and back offices whether 30 stock exchanges are open, when they close early and when a trade settles, and that links every fact it stores to the page it came from.

    Oct 24, 20269 minThe Engineering Logs

  6. 06

    HolidayOps: Building a Working Days API for 250 Countries

    How I built HolidayOps on my own: a Django and React platform that tells payroll, HR and logistics teams whether any date is a working day in any country, with company calendars, iCal feeds, signed webhooks and a public site that needs no JavaScript.

    Oct 18, 20269 minThe Engineering Logs

Available for new work · --:--:-- PKT

Have an idea?

sales@ahmershah.dev
Book a call WhatsApp +92 370 4831994

On this site

  • 01About
  • 02Education
  • 03Certificates
  • 04Skills
  • 05Projects
  • 06Experience
  • 07Services
  • 08Testimonials
  • 09Contact

Pages

  • Projects
  • Blog
  • Saved articles
  • RSS
  • Privacy Policy
  • Terms of Service
  • llms.txt

Elsewhere · official profiles

26 links

Professional

Work history, code and credentials.

  • in/syedahmershah
  • company/syedahmershah
  • @ahmershahdev
  • syed-ahmer-shah
  • syed-ahmer-shah
  • u/syedahmershah
  • @syedahmershah
  • AWS@syedahmershah

Coding & credentials

Problem solving, verified badges.

  • u/syedahmershah
  • syedahmershah
  • users/syedahmershah
  • learner/syedahmershah

Google & business

The official listings.

  • Google Business Profileg.page
  • Syed Ahmer Shah
  • syedahmershah
  • Beaconssyedahmershah
  • syedahmershah

Social (@ahmershahdev)

Build logs, clips, threads.

  • @ahmershahdev
  • @ahmershahdev
  • @ahmershahdev
  • ahmershahdev
  • @ahmershahdev
  • @bluesky.ahmershah.dev
  • ahmershahdev

Design

Interfaces and visuals.

  • syedahmershah
  • syedahmershah

Direct

  • sales@ahmershah.dev

    New projects & quotes

  • support@ahmershah.dev

    Support & help

  • syedahmershahofficial@gmail.com

    Personal

  • +92 370 4831994

    Phone · WhatsApp

  • Hyderabad, Sindh, Pakistan

    Remote-first · Asia/Karachi (UTC+5)

Resume ↗
SYED AHMER SHAH

© 2026 Syed Ahmer Shah. All rights reserved.

PrivacyTermsRSS
ahmershah.dev