DEV Community

Cover image for Day 76 of #100DayOfCode — Building DevBoard: REST API and UI Polish
M Saad Ahmad
M Saad Ahmad

Posted on

Day 76 of #100DayOfCode — Building DevBoard: REST API and UI Polish

Yesterday, DevBoard became a complete two-sided platform. Today I added two things that take it from a functional app to a portfolio-ready one: a REST API built with DRF, so the data is accessible programmatically, and UI polish using Tailwind CSS, so it actually looks like something you'd want to show someone.


The REST API

DevBoard's HTML interface works great for browser users. But job board data is also useful to external clients: a mobile app, a browser extension, a third-party aggregator. That's what the API is for. I kept the API focused on the most useful resource: job listings.

What the API Exposes

I made a deliberate decision to keep the API read-only for public consumers and write-enabled only for authenticated employers. The reasoning is straightforward: anyone should be able to fetch job listings programmatically, but only authenticated employers should be able to create or modify them through the API. This mirrors the permissions on the HTML side exactly.

The API exposes three endpoints:

  • GET /api/jobs/ — list all active job listings with full details
  • GET /api/jobs/<pk>/ — retrieve a single job listing
  • POST /api/jobs/ — create a job listing (employers only, requires token)

I chose not to expose applications through the API for now. Application data is sensitive; it contains personal information and cover letters, and there's no clear use case for exposing it externally at this stage.

Serializers

Writing the JobSerializer was interesting because of the tech stack field. In the database, it's stored as a comma-separated string, but in the API response, it should be a proper list. That's what any client consuming the API would expect. DRF's SerializerMethodField handled this cleanly. I added a method that calls the model's existing get_tech_stack_list() helper and returns the result. The database stores a string, the API returns a list, the model method bridges the two.

I also added a company_name field to the serializer that reaches into the related EmployerProfile, again using SerializerMethodField. This means API consumers get the company name directly in the job object without having to make a second request to look up the employer. Fewer requests, better API design.

ViewSet and Router

I used ModelViewSet with a custom get_permissions() method to handle the split between public read access and authenticated write access. The router registered it in two lines. What took twenty lines in the HTML views took four in the API.

The browsable API interface DRF provides out of the box was immediately useful for testing. Visiting /api/ in the browser shows all registered endpoints. Clicking through to /api/jobs/ shows the current job listings as formatted JSON with a form for POST requests right there on the page. No Postman needed during development.

Token Authentication for the API

I wired the existing token authentication from day 71 into the API. Employers who already have a token from registering can use it immediately to create job listings through the API. No separate authentication system to build.

Testing the full flow with Postman confirmed it worked: POST to /api/token/ with employer credentials, receive a token, include it as Authorization: Token <token> in a POST to /api/jobs/, job gets created. The same job immediately appears in the HTML interface too; one database, two interfaces.


UI Polish with Tailwind CSS

The app worked, but looked like raw HTML. I added Tailwind CSS via CDN — one script tag in base.html, and went through every template applying utility classes. No custom CSS file needed.

What Changed Visually

The job listings page went from a plain list to a proper card grid. Each card has a white background, subtle shadow, rounded corners, and a hover state. The tech stack tags became small, rounded badges with a light blue background. The search and filter bar is now a clean horizontal row that sits above the results.

The job detail page got the most attention. The header section has the company name, location, and meta details arranged in a visually clear hierarchy. The tech stack tags match the listing cards. The apply button is prominent — large, full-width on mobile, with a clear visual distinction between the four states: a blue button for candidates who haven't applied, a green badge for accepted, a grey badge for pending, and a red badge for rejected.

The employer dashboard became a proper data table with alternating row colors, clear column headers, and action buttons that look like buttons. The application count links are now visually distinct from plain text.

Navigation in base.html got a dark top bar with the DevBoard logo on the left and user-specific links on the right. Logged-out users see Login and Register buttons. Employers see Dashboard and Post a Job. Candidates see Browse Jobs and My Applications.

Mobile Responsiveness

Tailwind's responsive prefixes made mobile layout straightforward. The job card grid collapses to a single column on small screens. The search form stacks vertically on mobile. The navigation collapses the right-side links. Not a full responsive redesign, but the app is usable on a phone now.


A Few Small Improvements Along the Way

While polishing the UI, I caught a few things worth fixing:

The login page had no redirect handling; after logging in, users always went to the homepage regardless of what they were trying to do before. I added ?next= handling in the login template so candidates who get redirected to login while trying to apply land back on the application page after logging in.

The registration pages now have links to each other: "Already have an account? Login" and "Looking to hire? Post a job instead." Small thing that makes the onboarding feel complete.

The employer dashboard now shows a zero-state message when no jobs have been posted yet, with a prominent link to post the first one. Same on the candidate dashboard for zero applications.


Where DevBoard Stands After Day 76

The app now has three layers working together: an HTML interface for browser users, a REST API for programmatic access, and a consistent visual design that makes it presentable. The data layer underneath serves all three without any duplication.

Thanks for reading. Feel free to share your thoughts!

Top comments (0)