A secure backend engine for an Online Judge system built using Node.js, Redis, and C++.
It compiles user code safely, runs it against test cases, and returns accurate verdicts.
- Secure execution of user C++ programs
- Task queue using Redis + Bull
- Google OAuth 2.0 authentication
- JWT-based authorization
- Local file storage for solutions and I/O files
- Verdict system:
- ACCEPTED
- WRONG ANSWER
- COMPILATION ERROR
- TIME LIMIT EXCEEDED
- Node.js (Express)
- MongoDB (Mongoose)
- Redis + Bull
- G++ Compiler
- Google OAuth + JWT
- Multer & FS
Make sure these are installed on your machine:
- Node.js (v18+)
- MongoDB
- Redis
- G++ Compiler
This project requires Redis to manage the job queue. Without it, the worker cannot function.
- For Windows:
- Download the Windows Installer (.msi) from Memurai (Redis for Windows) or use WSL.
- Run the installer and finish the setup.
- Open Task Manager > Services and ensure
MemuraiorRedisis Running.
- For Mac/Linux:
# Mac brew install redis brew services start redis # Linux sudo apt install redis-server sudo service redis-server start
- Verify it works:
Open a terminal and type:
redis-cli ping # It must reply: PONG
git clone https://github.com/rkt0209/Code-Judge.git
cd Code-Judge
npm install
Create a .env file inside the root directory.
Server
PORT=5000
NODE_ENV=development
MongoDB
MONGO_URL=mongodb://127.0.0.1:27017/code-judge
Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
JWT
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRE=30d
Google OAuth
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
USER_REDIRECT_URI=http://localhost:5000/api/user/auth/redirect
ADMIN_REDIRECT_URI=http://localhost:5000/api/admin/auth/redirect
mkdir uploads
mkdir processing
-You can use docker directly ,then directly jump to Testing Step 0
docker compose up --build
- if you are not using docker then You need two terminals
npm run dev
node worker.js
-Since this is a backend-only project, use Postman to simulate the frontend.
-
Open your browser and visit: http://localhost:5000/api/user/auth
-
Sign in with your Google account.
-
The browser will show a JSON response. Copy the token string.
- Open your browser and visit: http://localhost:5000/api/admin/auth
- Sign in with your Google account.
- The browser will show a JSON response. Copy the token string.
-
Endpoint: POST /api/admin/questions/create
-
Auth: Select "Bearer Token" and paste your Admin Token.
-
Body (form-data):
title: "Sum of Two Numbers"
content: "Read two integers and print their sum."
tags: "math,easy"
input_file: [Select a text file containing 10 20]
solution_file: [Select a text file containing 30]
- Response: Copies the _id of the created question.
-
Endpoint: GET /api/user/questions
-
Auth: No authentication required
-
Response Example:
{
"message": "Questions Fetched Successfully",
"count": 2,
"data": [
{
"_id": "507f1f77bcf86cd799439011",
"title": "Sum of Two Numbers",
"content": "Read two integers and print their sum.",
"time_limit": 2,
"difficulty": "easy",
"createdAt": "2026-02-22T10:00:00.000Z"
}
]
}-
Endpoint: GET /api/user/questions/:question_id
-
Auth: No authentication required
-
Response: Returns the question with full description.
-
Endpoint: POST /api/user/submission
-
Auth: Select "Bearer Token" and paste your User Token.
-
Body (form-data):
question_id: [Paste the ID from Step 1]
submission_file: [Select your C++ file]
- Result: The server will return ACCEPTED if your code matches the solution.
Endpoint: GET /api/user/submission/history
Auth: Bearer Token (User Token)
You are asking a very sharp question about the system architecture.
Currently, you are seeing ACCEPTED in Postman because your code is running in Synchronous Mode (Blocking). The API server is politely waiting for the Worker to finish before replying to you.
To see the "Queued" message and the PENDING status, we must switch the API to Asynchronous Mode (Non-Blocking).
Here is exactly what happens in both scenarios:
Scenario 1: Current Setup (Synchronous / Blocking)
β’ Behavior: The API adds the job to the queue and pauses (await job.finished()). It waits until the Worker says "I'm done!"
β’ Postman Response: You wait 1-2 seconds, then get the final result:
JSON
{ "status": "ACCEPTED", "execution_time": 0.05 }
β’ Why use this? Good for simple sites where users want immediate feedback.
Scenario 2: Heavy Load Setup (Asynchronous / Non-Blocking)
β’ Behavior: The API adds the job to the queue and replies immediately. It does NOT wait for the worker.
β’ Postman Response: You get an instant response (in milliseconds):
JSON
{
"message": "Submission Queued! We are processing it in the background.",
"status": "PENDING"
}
β’ What happens to the Verdict? Since the worker hasn't finished yet, the status in the database is set to PENDING.
β’ How does the user see "ACCEPTED"? The user must check the History API (GET /history) a few seconds later. By then, the worker will have finished and updated the database from PENDING β ACCEPTED.
________________________________________
How to see the "Queued" & "PENDING" status right now
To experience this, you need to swap the code in controllers/user/submission.js.
1. Open controllers/user/submission.js 2. Comment out the "Blocking" logic and Uncomment the "Non-Blocking" logic.
2. Now Uncomment the async function submitFile (there are two functions with same name),uncomment the one here working of redis queue is written
3. Now on submission the api will reply immediately without waiting the submission is queued.
4. After on worker terminal the output will displaced a bit late based on the queue size.
5. After output display when you will check the submission history then it will show the actual result of compilation.
6. If you use the another function commenting this one the api will wait until execution I done, and as the output is displaced on worker terminal the response will be send on postman.
The Experiment
1. Restart Server: npm run dev.
2. Restart Worker: node worker.js.
3. Postman: Send a submission.
4. Result:
o Postman immediately returns: "status": "PENDING".
o Terminal 2 (Worker) wakes up a moment later: Job Received... Compiled... Verdict: ACCEPTED.
o Postman History: If you check GET /history now, that same submission will now say "status": "ACCEPTED".
The "Sandbox" prevents user code from crashing the server. To test this, try submitting these malicious/broken codes:
Submit this code to see how the system kills the process after the time limit.
#include <iostream>
using namespace std;
int main() {
while(true) {
// This runs forever
}
return 0;
}- Observation: The Worker logs
β οΈ Process killed due to timeout! and the verdict is TIME LIMIT EXCEEDED.
- MongoDB and Redis must be running
- Do not upload
.envto GitHub
Instead of running services manually, you can spin up the entire ecosystem (Mongo, Redis, API, Worker) with one command.
- Install Docker Desktop.
- Run:
docker-compose up --build
- Access: The API is now running at
http://localhost:5000.- Note: Code execution happens inside the Linux container, ensuring a consistent environment.
The frontend is a modern React application with Vite. It provides an interactive dashboard for users to view questions and submit code.
- π Browse all available coding problems
- π Google OAuth authentication (User & Admin)
- π» Live code editor with C++ syntax support
- β‘ Real-time submission results
- π Submission history tracking
- π― Responsive design for all devices
- π Automatic JWT token management
cd frontendnpm installnpm run devThe frontend will be available at http://localhost:3030
Important: Make sure the backend is running on http://localhost:5000 before starting the frontend!
- Welcome screen with "Sign In" button
- User chooses to sign in as User or Admin
- Dashboard displays all available questions
- Left Panel: Question list with difficulty badges and time limits
- Right Panel:
- Question details on top
- Code editor below
- Results displayed after submission
1. User selects a question
2. Writes C++ solution in the editor
3. Clicks "Submit" button
4. Frontend converts code to .cpp file
5. Sends to API endpoint /api/user/submission
6. Results displayed:
- ACCEPTED β
- WRONG ANSWER β
- COMPILATION ERROR β οΈ
- TIME LIMIT EXCEEDED β±οΈ
frontend/
βββ src/
β βββ components/
β β βββ Navbar.jsx # Top navigation bar
β β βββ AuthModal.jsx # Sign in modal window
β βββ context/
β β βββ AuthContext.jsx # Global auth state management
β βββ pages/
β β βββ Dashboard.jsx # Main dashboard with editor
β βββ services/
β β βββ api.js # API client with axios
β βββ styles/
β β βββ global.css # All styling
β βββ App.jsx # Main app component
β βββ main.jsx # React entry point
βββ index.html # HTML template
βββ vite.config.js # Vite configuration
βββ package.json # Dependencies
- User clicks "Sign In"
- Redirected to Google OAuth
- Backend exchanges code for JWT token
- Token stored in browser localStorage
- Frontend automatically adds token to all API requests
- No manual token copy-paste needed! β¨
- Click logout button to clear token and return to landing page
- All localStorage data is removed
Frontend communicates with these backend endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/user/questions |
GET | Fetch all questions |
/user/questions/:id |
GET | Get question details |
/user/submission |
POST | Submit code for evaluation |
/user/submission/history |
GET | View submission history |
/user/auth |
GET | OAuth flow for users |
/admin/auth |
GET | OAuth flow for admins |
npm run buildOutput goes to dist/ folder
npm run previewEdit src/styles/global.css and modify CSS variables:
:root {
--primary: #3b82f6; /* Change primary color */
--secondary: #10b981; /* Change secondary color */
--danger: #ef4444; /* Change danger color */
}Edit the code-editor class in global.css for different color schemes.
- Components go in
src/components/ - New pages go in
src/pages/ - API calls use
src/services/api.js - State management with React Context in
src/context/
Frontend runs on http://localhost:3030 and backend on http://localhost:5000.
Important: Backend must have CORS enabled for http://127.0.0.1:3030 in app.js:
app.use(cors({
origin: "http://127.0.0.1:3030"
}));If you see CORS errors, verify this configuration!
npm run devnode worker.jscd frontend
npm run devAll services working together! π
The frontend is fully responsive:
- Desktop (1024px+): Two-column layout with question list and editor
- Tablet (760px-1024px): Stacked layout
- Mobile (< 760px): Full-width components with optimized spacing
- Ensure backend is running on
http://localhost:5000 - Check CORS settings in backend
app.js - Clear browser cache and localStorage
- Verify Google Client ID and Secret in backend
.env - Check redirect URIs match in Google Console
- Ensure backend is accessible from browser
- Check JWT token in browser DevTools > Application > LocalStorage
- Verify backend worker is running
- Check API endpoint returns 200 status
- Use browser DevTools to inspect network requests
- Check localStorage (
JSON.parse(localStorage.getItem('user_data'))) - Monitor browser console for error messages
- Use backend logs to debug submissions
- Multi-language support (Python, Java, JavaScript, etc.)
- Admin dashboard for creating and managing questions
- Advanced code editor with syntax highlighting (Monaco/Prism)
- Leaderboard and user rating system
- Contest management and live scoring
- Dark mode theme
- Search and filter questions
- Submission statistics and analytics
Pull requests are welcome.
If you like this project, please β the repo!