Skip to main content

Command Palette

Search for a command to run...

I Built a Full-Stack App Using AI Without Understanding 80% of the Code

Updated
10 min readView as Markdown
I Built a Full-Stack App Using AI Without Understanding 80% of the Code
N
MERN Stack Developer passionate about building real-world web applications with MongoDB, Express.js, React.js, and Node.js. I write about full-stack development, JavaScript, backend engineering, APIs, authentication, system design, and lessons learned while building projects.

I had a working application.

The frontend looked good.

The backend was running.

The database was connected.

The API calls were working.

Authentication was implemented.

I could even show the project to someone and say:

"Yeah, I built this."

There was just one problem.

I didn't understand most of the code.

And the worst part?

I had written almost none of it myself.

AI had.


How It Started

I wasn't trying to cheat.

I was trying to build something.

Like most developers, I had an idea for a full-stack application but didn't want to spend weeks getting stuck on small implementation details.

So I opened an AI coding assistant and started asking questions.

At first, the questions were simple.

"Create an Express server."

Then:

"Add MongoDB connection."

Then:

"Create authentication using JWT."

Then:

"Add protected routes."

Then:

"Connect the React frontend."

Every time I got stuck, I asked AI.

And it kept giving me code.

The scary part was that the code worked.

So I kept going.


The AI Was Faster Than Me

This is where things became addictive.

I could describe what I wanted in a few sentences and get hundreds of lines of code.

For example:

"Create a login API with JWT authentication, bcrypt password hashing, validation and error handling."

A few seconds later:

const loginUser = async (req, res) => {
    try {
        const { email, password } = req.body;

        const user = await User.findOne({ email });

        if (!user) {
            return res.status(401).json({
                message: "Invalid credentials"
            });
        }

        const isMatch = await bcrypt.compare(
            password,
            user.password
        );

        if (!isMatch) {
            return res.status(401).json({
                message: "Invalid credentials"
            });
        }

        const token = jwt.sign(
            { id: user._id },
            process.env.JWT_SECRET,
            { expiresIn: "1d" }
        );

        res.status(200).json({ token });

    } catch (error) {
        res.status(500).json({
            message: "Server error"
        });
    }
};

I looked at it and thought:

Nice.

Then I moved to the next feature.

That was my mistake.


I Was Measuring Progress by Features

My mental checklist looked like this:

Authentication       ✅
MongoDB              ✅
CRUD APIs            ✅
React UI             ✅
Protected routes     ✅
Error handling       ✅
Deployment           ✅

Everything looked great.

The application was growing.

But something else was happening at the same time.

My understanding wasn't growing at the same speed.

I was becoming very good at asking AI for features.

I wasn't becoming equally good at building those features myself.


Then Something Broke

Eventually, something stopped working.

It wasn't even a huge bug.

The frontend was making an API request.

The API was returning an error.

So I did what I had been doing for weeks.

I copied the error into AI.

"Fix this error."

AI suggested a change.

I applied it.

The error changed.

So I sent the new error.

Another fix.

Another error.

Another prompt.

Another fix.

At some point, I realized I was doing this:

Bug
 ↓
Ask AI
 ↓
Copy solution
 ↓
New bug
 ↓
Ask AI
 ↓
Copy solution
 ↓
New bug

I wasn't debugging.

I was forwarding errors.

That sentence honestly hurt when I realized it.


The 80% Moment

The real wake-up call happened when I tried to explain my own project.

Someone asked me:

"Why did you use middleware here?"

I knew the word middleware.

But I struggled to explain exactly why that middleware was needed at that point in the request lifecycle.

Then:

"Where is the JWT verified?"

I opened the project.

I searched for jwt.verify.

I found the code.

But finding the code isn't the same as understanding it.

Then came another question:

"What happens if the token is expired?"

I knew there was some error handling.

But I couldn't confidently explain the complete flow.

That's when I realized:

I had built an application I couldn't properly walk through.

Maybe 80% is an exaggeration.

Maybe it was 70%.

Maybe 60%.

The exact percentage doesn't matter.

The feeling does.

The code was mine, but the understanding wasn't.


AI Didn't Actually Build My Project

This is something I understand differently now.

AI wasn't the villain.

I was using it badly.

There is a huge difference between:

"AI wrote this code and I accepted it."

and:

"AI helped me understand and implement this code."

The first one can make you productive very quickly.

The second one actually makes you better.

I had optimized for the first.


I Started Reading the Code I Had Ignored

So I changed my approach.

Instead of asking:

"How do I add this feature?"

I started asking:

"Explain what my existing code is doing."

That small change made a huge difference.

For example, instead of asking AI to create authentication from scratch, I took my existing authentication code and asked myself:

What happens when a user logs in?

POST /login
      ↓
Route
      ↓
Controller
      ↓
Find user
      ↓
Compare password
      ↓
Generate JWT
      ↓
Send response

Then I asked:

Why?

Why is the password hashed?

Why can't I store the password directly?

Why is JWT signed?

Why does the server need to verify the token?

Why is middleware involved?

Why does the controller receive req.user?

Those questions were much more valuable than:

"Give me JWT authentication code."


I Started Breaking the AI-Generated Code

This became one of my favorite learning techniques.

If AI generated something, I didn't automatically trust it anymore.

I tried changing it.

For example:

What happens if I remove this middleware?

What happens if I change the status code?

What happens if the request body is empty?

What happens if the user doesn't exist?

What happens if the token is invalid?

What happens if MongoDB is unavailable?

I wanted to see the application fail.

Because failure showed me what the code was actually doing.


I Learned More From One Bug Than From 100 Prompts

Before using AI heavily, I thought getting the correct answer was the goal.

Now I think understanding the problem is more important.

Suppose I get:

401 Unauthorized

The useful question isn't immediately:

"How do I fix 401?"

The useful questions are:

Who returned 401?

Why was 401 returned?

Did the request contain a token?

Was the token valid?

Did the middleware run?

Did the user exist?

Did authorization fail?

Once I started thinking this way, debugging became much less dependent on AI.


AI Can Make You Feel More Skilled Than You Are

This is probably the biggest danger.

When AI gives you working code quickly, your project can become much more advanced than your actual understanding.

You might have:

React
+
Node.js
+
Express
+
MongoDB
+
JWT
+
WebSockets
+
Cloudinary
+
Redis

on your resume.

But if someone asks:

"Walk me through one request from the browser to the database."

and you can't do it...

there's a problem.

Your technology stack has grown faster than your fundamentals.


The Resume Problem

This also changed how I think about putting projects on a resume.

Previously, I thought:

More technologies = stronger project.

Now I think:

Better understanding = stronger project.

I would rather confidently explain:

React
Node.js
Express
MongoDB
JWT

than list 15 technologies and struggle when an interviewer asks about them.

Because interviewers don't care that you used a library.

They care whether you understand what you built.


What I Still Use AI For

I haven't stopped using AI.

Actually, I use it a lot.

But I changed how I use it.

Before

"Build authentication for my MERN application."

Now

"Here is my authentication code. Explain the request lifecycle and identify any security or design problems."


Before

"Fix this error."

Now

"Here is the error and my code. Give me three possible causes and show me how to verify each one before changing the code."


Before

"Create a controller."

Now

"Explain what responsibilities should belong in this controller and what should stay outside it."


Before

"Write this function."

Now

"Give me the approach first. I'll try implementing it, then review my implementation."


My New Rule: Understand Before Accepting

I now have a simple rule for AI-generated code:

If I can't explain it, I don't consider it finished.

It doesn't mean I need to understand every line of every dependency.

Obviously, I don't need to understand the source code of Express before using Express.

But I should understand the code I am responsible for.

If I write:

const token = jwt.sign(
    { id: user._id },
    process.env.JWT_SECRET
);

I should understand:

  • What jwt.sign() does

  • What is being stored in the payload

  • What the secret is doing

  • How the token will later be verified

  • What happens if verification fails

That's enough.

The goal isn't to become the author of every line.

The goal is to be responsible for the system you're building.


AI Isn't Replacing Learning

I used to think AI would make learning programming unnecessary.

Now I think almost the opposite.

AI makes understanding more important.

Because generating code is becoming easier.

The valuable skill is increasingly:

knowing what code should exist in the first place.

Anyone can ask:

"Build me a REST API."

The better developer can ask:

"Do I even need this API?"

Anyone can generate authentication.

The better developer can ask:

"What are the security implications of this implementation?"

Anyone can generate a database schema.

The better developer can ask:

"Why should this data be modeled this way?"

AI can generate answers.

You still have to decide which answer makes sense.


The Biggest Lesson I Took From This

I don't regret building the application with AI.

I regret outsourcing my understanding along with the implementation.

There is nothing wrong with using AI to move faster.

There is something wrong with becoming dependent on it for every decision.

The goal shouldn't be:

"I built a full-stack app with AI."

The better goal is:

"I used AI to build a full-stack app, and I understand what I built."

That's a completely different achievement.


If You're Using AI to Build Projects

Try this experiment.

The next time AI gives you a piece of code:

Don't immediately paste it.

First ask:

1. What problem is this solving?

2. Why does this approach work?

3. What could go wrong?

4. Can I explain this code without looking at the AI response?

5. Can I modify it without asking AI?

If the answer to the last two is no, spend some time learning it.

Then use the AI again.

Not as your replacement.

As your pair programmer.


Final Thought

My application worked.

That wasn't the problem.

The problem was that I had confused a working application with understanding the application.

AI made it incredibly easy for me to cross the finish line.

But when I looked back, I realized I had skipped a large part of the journey.

So now, whenever I use AI to build something, I try to remember one thing:

Don't just make the code work. Make sure you know why it works.

Because the day the AI-generated code breaks in production...

there won't be a prompt box inside your head.

You'll have to understand the system yourself.