Building a Backend from Scratch at Work — Bugs, TypeScript Struggles, and Finally a Green CI Build

If you're building a backend from scratch at work, especially using TypeScript and CI tools like GitHub Actions, you're going to face more than just writing APIs.

When we build personal projects, backend development feels pretty simple. Create a few APIs, connect the database, test locally… done.

But real-world development at work?

It hits very differently 😅

Recently, I was assigned a task that sounded small at first:

“There are no APIs for this feature. Please build the backend.”

That one sentence meant something big — no existing routes, no controllers, no services… nothing. Everything had to be designed and developed from scratch.

At first, I felt excited. Building a backend myself sounded fun. But within a few hours, that excitement slowly turned into debugging, TypeScript errors, database issues, security warnings, and GitHub Actions failures.

It was messy. Frustrating. Challenging.

But honestly?

It became one of the best learning experiences of my developer journey so far.

folder structure

Step 1 – Starting With a Clean Backend Structure

Before writing random code, I didn’t want to create a “spaghetti backend”. So I followed a proper layered architecture like we see in production apps.

models/
services/
controllers/
routes/

Each folder had a clear responsibility:

  • Models → database schema
  • Services → business logic
  • Controllers → handle requests & responses
  • Routes → define API endpoints

This separation made everything feel professional and scalable. I remember thinking, “Nice… this is going smoothly.”

Spoiler alert: it didn’t stay smooth for long 😂

Challenge #1 – TypeScript Started Fighting Me

The backend was written completely in TypeScript.

And if you’ve mostly worked with JavaScript before, TypeScript can feel very strict at first. It doesn’t let anything slide.

Suddenly I started seeing errors like:

  • Property does not exist on type
  • Object possibly undefined
  • Type not assignable
  • Missing return type

Even simple code refused to compile.

At one point I literally stared at the screen thinking, “Bro… the value is clearly there. Why are you complaining?” 😭

But slowly I understood what was happening. TypeScript wasn’t blocking me — it was protecting me.

So I started doing things properly:

  • Creating interfaces
  • Typing request/response objects
  • Defining DTOs
  • Avoiding any

And something interesting happened…

My runtime bugs reduced a lot.

Lesson learned: TypeScript feels painful at first, but it saves hours of debugging later.

If you want a deeper guide on debugging APIs, I’ve explained my exact process here: API Debugging Guide.

Challenge #2 – The Database Performance Surprise (Compound Index)

After finishing the APIs, I pushed my code and waited confidently for CI to pass.

Instead…

🚨 High priority warning.

Turns out some queries were slow because I forgot to add a compound index.

The queries were filtering multiple fields like:

find({ userId, createdAt })

Without indexing, the database scans the entire collection. Which means:

  • slow performance
  • heavy queries
  • bad scalability

This wasn’t just a bug — it was a production-level performance issue.

db index

The fix was simple:

schema.index({ userId: 1, createdAt: -1 })

After adding this, queries became much faster and the warning disappeared.

That’s when I realized something important:

Backend isn’t just about APIs. It’s also about performance.

Challenge #3 – My Logging Mistake (Security Issue)

While debugging authentication, I did what every developer does:

console.log(token)
console.log(user)

It helped during debugging. But GitHub Actions immediately flagged it as a security issue.

Because logging sensitive data like:

  • tokens
  • passwords
  • user details

can expose secrets in production logs.

Honestly, I never thought deeply about this before. But now it made sense.

safe-logging

So I replaced unsafe logs with safe ones:

logger.info("User logged in", { userId })

Small change. Big improvement.

Lesson learned: Never log secrets, even during development.

Challenge #4 – GitHub Actions Kept Failing

Every push triggered:

  • tests
  • lint checks
  • security validations
  • build verification

And many times… it failed.

Seeing a red ❌ again and again is honestly stressful. You feel like nothing is working.

But over time, I understood something:

CI is not your enemy. It’s your safety net.

It forced me to:

  • write cleaner code
  • fix edge cases
  • remove bad practices
  • follow standards

Finally, after fixing everything…

ci-success

🟢 All checks passed.

That green build felt SO satisfying. Way more satisfying than writing the APIs themselves.

Final Backend Flow

Frontend → Routes → Controllers → Services → Database

Clean. Structured. Maintainable. Exactly how a backend should feel.

What This Experience Taught Me

  • TypeScript prevents hidden bugs
  • Architecture matters a lot
  • Database indexing is critical
  • Security should never be ignored
  • CI improves quality
  • Debugging is normal — not failure

Most importantly, I learned that real backend development is not smooth or perfect. It’s messy, and that’s completely okay.

Conclusion

This task started as “just build some APIs”.

But it ended up teaching me: performance, security, architecture, and debugging — all in one project.

If you’re learning backend development, don’t expect everything to work on the first try. It won’t.

Break things. Fix them. Learn from them.

Because that’s how you actually become an engineer.

And honestly? This messy journey taught me more than any tutorial ever could.

Related Articles

If you are improving your development skills, these guides will help you.

Comments

Popular posts from this blog

What Is This Keyword in JavaScript? Explained With Real Examples

How to Navigate a Large Codebase (Frontend & Backend Guide for New Developers)

How JavaScript Objects Work (Prototype Chain, Inheritance & Property Descriptors Explained)