- Weak Separation of Concerns
- Domain Layer Leaking HTTP Concepts
- Missing or Improper Dependency Injection
- Missing Persistence or Improper Storage Design
- Weak or Absent Automated Testing
- Inconsistent API Contracts Between Frontend and Backend
- DOM Manipulation Instead of State-Driven Rendering
- Fragile Data Modeling
Handlers, domain logic, persistence logic, and routing are mixed together in the same modules.
Instead of having layers like:
routes → handlers → domain → persistence
projects collapse everything into one layer.
Without separation:
- Testing becomes difficult
- Refactoring becomes dangerous
- Code reuse becomes impossible
- Bugs propagate across layers
This is one of the first architectural instincts engineers must develop.
Bad pattern:
app.post("/task", async (c) => {
const body = await c.req.json();
todos.push(body);
await Deno.writeTextFile("db.json", JSON.stringify(todos));
return c.json({ ok: true });
});Here the route:
- parses HTTP
- mutates domain state
- writes to disk
All inside the same function.
Introduce clear layers:
app.js → handler.js → todoManager.js → storage.js
Example:
export const createTaskHandler = (manager) => async (c) => {
const task = await c.req.json();
const result = manager.createTask(task);
return c.json(result);
};- Nikhil Chodavarapu / Dinesh Kumar
- Sorangi Gopi Vinay / Priyanshu Arya
- Javed Ahmed / Vismaya P S
Domain classes return HTTP responses or HTTP-style objects instead of domain objects.
Example:
return { success: true, status: 200 };The domain layer should not know transport protocols.
If domain objects depend on HTTP semantics:
- They cannot be reused in CLI tools
- They cannot be reused in different APIs
- They mix business logic with transport logic
Bad:
class TodoManager {
addTask(task) {
if (!task) {
return { status: 400, error: "invalid task" };
}
tasks.push(task);
return { status: 200 };
}
}Domain layer returns domain values or throws domain errors.
class TodoManager {
addTask(task) {
if (!task) throw new Error("invalid task");
tasks.push(task);
return task;
}
}
Handlers translate this to HTTP:
try {
const task = manager.addTask(data);
return c.json(task);
} catch {
return c.json({error:"invalid task"}, 400);
}
- Amisha Rawat / Jyothi Praveen Gorre
- Som Nath Gupta / Karthik Dasari
- Rahul Vishwakarma / Peddakota Bhargavi
Shared state like todoManager or db is imported globally instead of injected.
Example:
import todoManager from "./todo_manager.js"
Without DI:
- Tests cannot swap dependencies
- State becomes hidden global state
- Multiple app instances become impossible
Dependency injection makes systems composable and testable.
Bad:
import db from "./db.js"
export const createTask = (c) => {
db.tasks.push(...)
}
Inject dependencies through middleware or constructor.
Example:
app.use((c, next) => {
c.set("todoManager", todoManager);
return next();
});Then handlers read from context.
- Nikhil Chodavarapu / Dinesh Kumar
- Sorangi Gopi Vinay / Priyanshu Arya
(Some others implemented DI correctly.)
Projects remain in-memory only, or persistence is implemented incorrectly.
Phase-2 required data to survive server restarts .
Persistence is fundamental to real applications.
Without persistence:
- Systems lose state
- Testing production behavior becomes impossible
- Restarting servers wipes user data
Bad:
let todos = [];
Better:
readFile("db.json")
writeFile("db.json")
Or a database abstraction.
Introduce a storage abstraction:
TodoManager(storage)
Where storage could be:
- JSON file
- SQLite
- memory
This enables easy swapping and testing.
- Vivek Bhardwaj / Khasim Sheik
- Paulami Dutta / Sivaji Sai
- Samiran Gharami / Rashmika Gandra
- Adithyan K / Dilliswara Rao Galala
- Gautham Krishna T G / Sidhartha Maji
- Vikas Yadav / Chiranjeevi Gurram
- Sorangi Gopi Vinay / Priyanshu Arya
Either:
- No tests
- Only trivial tests
- Only unit tests but no route tests
Testing is the only scalable way to evolve systems safely.
Without tests:
- Refactoring becomes dangerous
- Regression bugs appear
- Edge cases are ignored
Weak test:
assertEquals(res.status, 200)
Better test:
assertEquals(res.body.task.text, "Buy milk")
Test three layers:
- Domain logic
- HTTP handlers
- Integration behavior
Example:
app.request("/task", {...})
- Nikhil Chodavarapu / Dinesh Kumar
- Shaik Rafiya / Dasari Nandini
- Som Nath Gupta / Karthik Dasari
- Rahul Vishwakarma / Peddakota Bhargavi
- Sanket Pawar / IBRAHIM BADUSHA KK
- Javed Ahmed / Vismaya P S
Frontend and backend disagree on request/response format.
Example:
Frontend sends:
POST /task?todoId=1
Backend expects:
{todoId:1}
When contracts drift:
- Bugs become difficult to trace
- Systems become brittle
- Integration breaks easily
Frontend expects:
{ data: ... }
Backend returns:
{ message: "ok" }
Define an API contract.
Example:
POST /todos/:id/tasks
→ returns {task}
Both sides implement against this.
- Som Nath Gupta / Karthik Dasari
- Umar Khaji / Adarsh P
- Javed Ahmed / Vismaya P S
Frontend directly manipulates DOM elements after operations.
Example:
element.remove()
instead of updating state and re-rendering.
Direct DOM mutation causes:
- UI state drift
- inconsistent rendering
- difficult debugging
State should be the single source of truth.
Bad:
button.onclick = () => {
li.remove()
}
Better:
tasks = tasks.filter(...)
render(tasks)
Adopt render-from-state architecture.
state → render → DOM
Many teams solved this by re-rendering the whole list after every mutation.
- Jeniffer P Jojo / Ayush Verma
- Javed Ahmed / Vismaya P S
- Abhay Pratap Singh / Sirisha Dalasari
Data structures make relationships fragile.
Example:
tasks = []
todos = []
task.todoId
with frequent filtering.
Or storing IDs in DOM strings:
task-1
todo-2
Weak models cause:
- inefficient lookups
- hidden bugs
- brittle coupling between frontend and backend
Bad:
tasks.filter(t => t.todoId === id)
Better:
todos = {
[todoId]: { tasks: {...} }
}
Use normalized structures.
todos -> tasks
or relational models.
- Javed Ahmed / Vismaya P S
- Shaik Rafiya / Dasari Nandini
- Himanshu Jaguri / Gajulapalli Mohanthi
- Nikhil Chodavarapu / Dinesh Kumar
Across the cohort, three core engineering instincts separate strong submissions from weak ones:
- Layered architecture
- Testing discipline
- State modeling
Teams that got these right produced clean, extensible systems.
Teams that missed them struggled with fragile code and integration bugs.