Back to blog
flaskvueceleryredisfull-stackiit-madras

Building a Placement Portal: Networks, Mistakes, and Five Hours with Celery

April 14, 20266 min read

Building a Placement Portal: Networks, Mistakes, and Five Hours with Celery

There's a particular kind of curiosity that hits when you're looking at something you don't fully understand — not anxiety, just a quiet pull. I want to know how this works. That was the feeling when I first read the project spec for MAD-II.

Build a placement portal. Three roles. Admin, Company, Student. Flask on the backend, Vue.js on the frontend. Redis, Celery to handle the async backend tasks. A system that actually does things (I don't?) — approvals, applications, scheduled jobs, async exports.

I said yes to all of it, even the parts I hadn't touched before.


Starting from the Ground

I didn't have much experience with networking or JavaScript going in. Not zero — but not confident either. The kind of familiarity where you can read code and follow it, but writing it from scratch feels like stepping onto ice (It stings sometimes..).

So I started where I was sure: the database.

SQLAlchemy let me think in terms I already understood — models, relationships, types. I designed the schema first: a unified User model with a role field to differentiate Admin, Company, and Student, then JobPosition, Application, Placement hanging off it in the right directions. Getting the relationships right — which was 1:N, which was N:N, where the foreign keys lived — that was methodical work. Satisfying in the way that fitting pieces together is satisfying.

The Admin had to be pre-seeded programmatically. No registration route. Just a script that ran after db.create_all() and inserted the superuser directly through the ORM. Small thing, but it felt good to get right.


The Part That Actually Broke Me

Flask-Security seemed like the obvious choice for authentication. Documented, respected, built for exactly this kind of role-based setup. I got it configured, wired up the login flow, and went to hash a password.

It failed. Silently.

What was actually happening

flask-security's hash_password was failing because Flask-Security doesn't bundle its hashing backend — you have to install it separately, and it isn't obvious that's what's missing. The function exists, the import works, and it still silently fails. After enough time staring at it, I switched to flask-bcrypt, which is self-contained and does exactly one thing. Same result, cleaner path.

That's a specific kind of frustrating — when the problem isn't your logic, it's the ground under your logic. You second-guess everything you wrote before you eventually find the thing that was never yours to fix. I found it. I switched. I moved on.


Learning to Think in Roles

Once auth was stable, the role-based access control started to click. Flask's decorator pattern made it expressible in a way that felt close to how you'd think about it: this route is for admins, this one for companies, this one for students. Wrong role hits the door and goes no further.

The dashboards came together role by role. Admin gets the full picture — approve companies, approve drives, blacklist users, search everyone. Company gets their own corner — post jobs, see applicants, move them through the pipeline. Student gets what they need and nothing else — browse drives, apply, track status, download confirmations.

Vue.js was the part I was least sure about. I used it via CDN — no CLI, no build step — which kept the setup simple but meant I had to think carefully about reactivity. When a modal opened, when a list updated, when a status changed: making the UI respond correctly without a proper component build system required attention. It was friction, not crisis. The kind of thing you work through.


Five Hours

Here's the part of this project I think about most.

Celery. Redis broker. Celery Beat for scheduled jobs. Async task execution. I had to learn it and implement it in the time I had available one day — five hours.

Not five hours of uninterrupted focus as a stylistic choice. Five hours because that's what the schedule allowed. Warehouse shift, other coursework, the MSc running in parallel. Five hours was the window.

What needed to be built in that window

Three job types:

  1. Interview reminders — a scheduled Celery Beat job that ran daily and notified students with upcoming interviews via email or webhook
  2. Monthly placement report — triggered on the first of each month, generating an HTML summary for the Admin
  3. CSV export — user-triggered from the student dashboard, runs async, sends a notification when done

Each of these required understanding the Celery worker lifecycle, how Beat schedules tasks, how Redis acts as the broker between them, and how to wire it all into an existing Flask app without breaking what was already working.

I read fast. I built as I understood. I broke things, traced why, fixed them, kept going. By the end of the window it was working — tasks queuing, Beat firing on schedule, the async export completing and notifying.

That's the moment I remember most from this project.


Caching, and the End of It

The last core piece was Redis caching on the frequently-hit endpoints — job listings, search results — with expiry policies and cache invalidation on writes. After Celery, it felt comparatively calm. Redis was already in the stack. The patterns were familiar enough.

There's something about implementing caching that makes a system feel real. You're not just making things work, you're thinking about what it costs to make them work repeatedly. That's a different kind of thinking.


What I Came Away With

This project didn't make me a backend engineer overnight. But it gave me something more durable than a skill — it gave me a model for how these systems fit together.

A request comes in. It hits a route. The route checks who you are. It talks to the database, maybe pulls from cache. If something heavy needs doing, it gets handed off to a worker. The worker runs later, somewhere else, and tells you when it's done.

That mental model — I didn't have it before this. Now I do. And it turns out to be the same model that runs under a lot of the more complex systems I want to work with next.

That's enough of a foundation to keep going.