> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing Effective Development Prompts

> Guide on crafting clear, contextual development prompts for LLMs to produce predictable code, showing patterns, examples, and an Express login endpoint implementation

In this lesson we'll cover how to write effective development prompts that reduce ambiguity, eliminate unnecessary exploration, and produce predictable, high-quality code from an LLM (here: Claude Code). Good prompts balance clarity with necessary context so the model makes useful assumptions rather than guessing your stack or file layout.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/sPr82USZL1DTm72J/images/Claude-Code-for-Beginners/Working-with-Claude-Code/Writing-Effective-Development-Prompts/writing-effective-development-prompts-demo.jpg?fit=max&auto=format&n=sPr82USZL1DTm72J&q=85&s=ab59f005fa124a7274fd18fd5d8b4b43" alt="A minimalist presentation slide that says &#x22;Writing effective development prompts&#x22; on the left and &#x22;Demo&#x22; inside a dark blue curved shape on the right. The bottom-left corner shows a small &#x22;© Copyright KodeKloud&#x22; label." width="1920" height="1080" data-path="images/Claude-Code-for-Beginners/Working-with-Claude-Code/Writing-Effective-Development-Prompts/writing-effective-development-prompts-demo.jpg" />
</Frame>

Why prompt quality matters

* Vague prompts cause the assistant to infer project type, frameworks, file structure, and other assumptions.
* Explicit prompts provide the context and constraints the assistant needs to produce focused, actionable results.
* The result: less back-and-forth, fewer surprises, and a predictable file scaffold.

Bad prompt example

* Prompt: "Make a login screen"

What happens with an ambiguous prompt
When you issue a one-liner like "Make a login screen", the assistant must guess critical details (is this Express, React, or another stack?) and often performs exploratory steps on your workspace. The example below shows the assistant probing the repository because it inferred an Express.js app from the folder name:

```bash theme={null}
Enter to confirm · Esc to exit

* Welcome to Claude Code!
/help for help, /status for your current setup
cwd: /Users/jeremy/Repos/Claude Code Course/Express-login-demo

> Make a login screen

· Finagling... (esc to interrupt)

I will create a login screen for your Express application. Let me first explore the project structure to understand the current setup and then plan the implementation.

• List(~/Repos/Claude Code Course/Express-login-demo)
  └ Listed 2 paths (ctrl+r to expand)

• Read(~/Repos/Claude Code Course/Express-login-demo)
  └ Error: EISDIR: illegal operation on a directory, read

• Bash:
  find /Users/jeremy/Repos/Claude\ Code\ Course/Express-login-demo -type f \( -name "*.js" -o -name "*.json" -o -name "*.html" -o -name "*.css" \) | head -20

Do you want to proceed?
1. Yes
2. No, and tell Claude what to do differently (esc)
```

Because the folder contains "Express-login-demo", Claude assumed Express and started scanning files. If the folder name were different, Claude might have assumed React, Vue, or another stack. Ambiguous prompts often trigger these kinds of exploratory behaviors.

Better prompt example
A concise, complete prompt lets Claude proceed without guessing. Below is a high-quality prompt that specifies the endpoint, inputs, validations, DB, hashing, token behavior, and error handling.

```text theme={null}
Create a POST /api/auth/login endpoint using Express.js with the following requirements:

- Input: email and password (both required)
- Validation: email must be a valid email; password must be at least 8 characters
- Use express-validator for input validation
- Look up users in PostgreSQL (pg) by email
- Compare passwords using bcrypt
- On success, return a JWT (24-hour expiration) that includes the user's id and email
- Response on success: `{"token": "...", "user": {"id": ..., "email": "...", "name": "..."}}`
- Implement comprehensive error handling:
  - Validation errors -> 400
  - Invalid credentials -> 401
  - Database connection errors -> 503
  - Generic server errors -> 500

Also create:
- A `.env` template for DB and JWT config
- A `schema.sql` to create the users table
```

Why this prompt works

* Context: establishes Express.js, PostgreSQL, and the endpoint path.
* Action: clearly states "Create POST /api/auth/login".
* Details: validation rules, hashing, token requirements, and error mappings.
* Examples: explicit success response format and expected status codes.

Typical generated to-do list (condensed)

* Initialize Node.js project (`package.json`)
* Install dependencies: `express`, `express-validator`, `bcrypt`, `jsonwebtoken`, `pg`, `dotenv`, and `nodemon` (dev)
* Create a basic Express app (`server.js`)
* Create DB connection (`config/database.js`)
* Implement `POST /api/auth/login` in `routes/auth.js`
* Add `.env` and `schema.sql`
* Add error handling and validation

Files created and their purpose

| File | Purpose | Example |
| - | - | - |
| `server.js` | Entry point: express app and routes | `node server.js` |
| `routes/auth.js` | Authentication routes, including `POST /api/auth/login` | See example below |
| `config/database.js` | Exports a `pg` Pool instance configured from `process.env` | `module.exports = pool` |
| `.env` | Environment variables for DB and JWT | See example `.env` |
| `schema.sql` | SQL to create `users` table | See example `schema.sql` |
| `package.json` | Project metadata and scripts | `npm run dev` |

Example `.env` template

```text theme={null}
PORT=3000
JWT_SECRET=your_jwt_secret_key_here
JWT_EXPIRES_IN=24h
# Database configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=express_login_demo
DB_USER=your_db_user
DB_PASSWORD=your_db_password
```

Example `package.json` snippet

```json theme={null}
{
  "name": "express-login-demo",
  "version": "1.0.0",
  "description": "Express.js login demo with JWT authentication",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["express", "login", "jwt", "authentication"]
}
```

Common HTTP error mapping

| Condition | Status Code | Example response |
| - | - | - |
| Validation errors | 400 | `{"errors": [...]}` |
| Invalid credentials | 401 | `{"message":"Invalid credentials"}` |
| Database connection error | 503 | `{"message":"Database connection error"}` |
| Generic server error | 500 | `{"message":"Internal server error"}` |

Minimal example of routes/auth.js (concise pattern)
This implementation demonstrates express-validator, bcrypt comparison, JWT generation, and consistent error handling.

```javascript theme={null}
// routes/auth.js
const express = require('express');
const { body, validationResult } = require('express-validator');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const pool = require('../config/database'); // pg Pool instance
const router = express.Router();

router.post(
  '/login',
  [
    body('email').isEmail().withMessage('Please provide a valid email'),
    body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { email, password } = req.body;

    try {
      const client = await pool.connect();
      try {
        const result = await client.query('SELECT id, email, name, password_hash FROM users WHERE email = $1', [email]);
        if (result.rowCount === 0) {
          return res.status(401).json({ message: 'Invalid credentials' });
        }

        const user = result.rows[0];
        const match = await bcrypt.compare(password, user.password_hash);
        if (!match) {
          return res.status(401).json({ message: 'Invalid credentials' });
        }

        const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET, {
          expiresIn: process.env.JWT_EXPIRES_IN || '24h'
        });

        return res.json({ token, user: { id: user.id, email: user.email, name: user.name } });
      } finally {
        client.release();
      }
    } catch (err) {
      console.error('Database error:', err);
      return res.status(503).json({ message: 'Database connection error' });
    }
  }
);

module.exports = router;
```

Note: Ensure `config/database.js` correctly exports a `pg` Pool instance configured via `process.env` values.

Database schema example
A simple `schema.sql` to create the `users` table used above:

```sql theme={null}
-- schema.sql
CREATE TABLE IF NOT EXISTS users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  password_hash TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT now()
);
```

Why the explicit prompt produced good output

* It specified the stack (Express.js), the exact route to implement, validation constraints, the database, hashing algorithm, token behavior, response formats, and expected status codes.
* With those constraints, the assistant generated a predictable file structure, `.env` template, and an implementation plan.

Prompt engineering formula (practical)
Use this pattern when you craft prompts:

Context → Action → Details → Examples

* Context: What exists already (project type, folder structure, relevant files)
* Action: Exactly what you want (add endpoint, create files, run tests)
* Details: Constraints, libraries, edge cases, and formats
* Examples: Example request/response or code snippets to clarify expected outputs

Helpful rules for good prompts

* Clear: Avoid ambiguity; name frameworks and endpoints.
* Complete: Include required validations, status codes, and response shapes.
* Contextual: Provide repository context or file layout if it matters.

<Callout icon="lightbulb" color="#1CB2FE">
  Tip: Spending a minute writing a clear, complete prompt can save many minutes of revisions. Aim for the Goldilocks zone — neither too vague nor overly verbose.
</Callout>

Common pitfalls to avoid

* Vagueness that leads the assistant to assume stack or file layout.
* Conflicting constraints that leave the model unsure which rule to follow.
* Omitting expected response formats (e.g., not specifying JSON structure).

References

* Express: [https://expressjs.com/](https://expressjs.com/)
* express-validator: [https://express-validator.github.io/docs/](https://express-validator.github.io/docs/)
* bcrypt: [https://www.npmjs.com/package/bcrypt](https://www.npmjs.com/package/bcrypt)
* jsonwebtoken: [https://www.npmjs.com/package/jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken)
* pg (node-postgres): [https://node-postgres.com/](https://node-postgres.com/)
* PostgreSQL docs: [https://www.postgresql.org/docs/](https://www.postgresql.org/docs/)

Summary

* Vague prompts cause LLMs to infer and explore; explicit prompts produce focused, actionable output.
* Use the Context → Action → Details → Examples pattern and the three Cs (clear, complete, contextual) to get consistent, useful results.
* When uncertain, include an example response or code pattern — examples guide the model to your intended shape of output.

This lesson demonstrates how precise prompts reduce guessing and produce predictable, high-quality code scaffolding and implementations. Apply these techniques when interacting with Claude Code or any LLM-assisted development tool.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/claude-code-for-beginners/module/3e896e50-3c07-4fdc-8603-bf125255d0a9/lesson/315966b6-3aff-4359-8d16-256590ea9599" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.