Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150426 stories
·
33 followers

Building Real AI Agents in 2026 — My Ongoing Journey Over the past few months, I...

1 Share

Building Real AI Agents in 2026 — My Ongoing Journey

Over the past few months, I’ve been digging into building agentic AI systems in Lunch and Learns with my co-workers.

Why We Care About Agents

We’ve already helped clients implement standard copilots—Chat‑Agent‑to‑RAG setups, Azure AI Search, custom knowledge grounding.

But agents open the door to something more: actions, not just answers.

An interesting example is ordering a pizza through the agent’s API, or pulling data from multiple systems and actually orchestrating tasks end‑to‑end.

Read the whole story
alvinashcraft
28 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Work With Dapper in .Net

1 Share

When you're working with .NET, interacting with databases (particularly SQL databases) is inevitable. Common approaches involve using ORM (Object Relational Mapping) with tools like Entity Framework.

Dapper stands out as a lightweight and high-performance ORM tool with numerous advantages. But where Dapper really excels is in its speed and control. Here, we'll explore Dapper's suitability for performance-critical .NET projects with simpler database relationships utilising raw SQL queries.

The article guides you in building a lightweight .NET Web API for a social media application with Dapper using a Postgres database.

Prerequisites

  • Latest .Net SDK and runtime (at time of writing, it's .Net 10).

  • Knowledge of the C# programming language and how Dependency Injection works

  • A running PostgreSQL instance

  • DB Viewer software, for example DBeaver, pgAdmin, or the PostgreSQL extension for VS Code

Table of Contents

What Is Dapper?

Dapper is a "micro ORM", providing lightweight, high-performance data access with minimal abstraction. It relies on SQL queries (these are the queries you use when interacting with an SQL database) for example:

SELECT * FROM influencers WHERE ID = 1, mapping the results to objects directly.

Here's how Dapper's README describes what it does:

This [Dapper] provides a simple and efficient API for invoking SQL, with support for both synchronous and asynchronous data access, and allows both buffered and non-buffered queries. – Dapper github repository README

An ORM (Object-Relational Mapper) is like a translator between a programming language and a database, allowing software to interact with databases using familiar programming objects (instead of direct SQL queries), making it easier to manage and manipulate data in applications.

As Dapper is a "Micro-ORM", it acts a middle-ground between direct SQL and a full fledged ORM like that of the Entity Framework (EF) which you may have heard of before. It has a lot of the basic features, but doesn't come with all the "bloat," making it a secure and faster database tool.

Challenges and Advantages of Dapper

Challenges of Dapper

Challenge Description
No Built-in LINQ Support Dapper lacks native LINQ integration, forcing developers to write raw SQL instead of using expressive LINQ queries for data filtering and projections. This can slow development for teams accustomed to LINQ in ORMs like EF Core.
Limited Conventions With fewer defaults and automations, Dapper requires explicit mappings and configurations for schemas or parameters, leading to more setup code compared to convention-heavy ORMs.
Fewer Features for Complex Data Models Handling intricate entity relationships (for example, deep hierarchies or many-to-many joins) demands custom SQL and manual mapping, increasing complexity in apps with rich relational data.
No Lazy Loading Related entities aren't loaded on-demand – everything must be eagerly fetched via multi-mapping or separate queries, potentially causing performance issues or extra code for deferred loading.
Manual SQL Writing Developers must craft all queries by hand, which can introduce errors, SQL injection risks (if parameters are mishandled), and more boilerplate, making maintenance tougher than auto-generated queries in full ORMs.
No Change Tracking Dapper doesn't monitor object changes, so updates and deletes require manual SQL, which can complicate write operations and lead to inefficiencies in CRUD-heavy apps.
Lack of Database Migrations No built-in tools for schema evolution or versioning, forcing reliance on external libraries or manual scripts – a stark contrast to EF Core's integrated migrations.
Connection Management Challenges While Dapper uses ADO.NET connections, improper handling (for example, not disposing properly) can lead to pooling issues or leaks, especially in high-concurrency web apps.

Advantages of Dapper

But let’s not dwell on the negatives. Now let's concentrate on the advantages, as these far outweigh the challenges.

Advantage Description
Minimal Abstraction As a micro-ORM, Dapper offers a thin wrapper over ADO.NET, giving developers direct access to SQL without heavy abstractions. This is ideal for those who want precise control over data access code and prefer working closer to the barebones.
Optimised for Read-Heavy Workloads Dapper shines in applications with frequent reads, allowing custom SQL optimisations for queries like reporting or dashboards, resulting in faster data retrieval compared to feature-rich ORMs.
High Performance Known for its lightweight design and low overhead, Dapper delivers extremely fast execution, especially with large datasets or high-throughput operations. It often outperforms Entity Framework in benchmarks for raw speed.
Full Control Over SQL Queries Developers write explicit SQL, enabling fine-tuned queries for complex scenarios; while this requires manual management, it avoids the "black box" issues of auto-generated SQL in other ORMs.
Flexible Result Mapping Dapper allows easy mapping of query results to custom objects or view models with custom logic, supporting features like multi-mapping for joins without rigid conventions.
Simplicity and Ease of Use With a small API surface and quick setup, Dapper has a low learning curve, making it accessible for rapid prototyping or integrating into existing .NET projects without bloat.
Support for Multiple Result Sets Using methods like QueryMultiple, Dapper efficiently handles queries returning several result sets in one call, reducing roundtrips to the database and boosting efficiency.
Built-in SQL Injection Protection Automatic parameterisation secures queries against injection attacks, combining raw SQL flexibility with safety out of the box.
Broad Database Compatibility Built on ADO.NET, Dapper works seamlessly with any database provider (for example, SQL Server, PostgreSQL), offering versatility without vendor lock-in.

Getting Started – Installation

You’re going to use the CLI (Command Line Interface) to install Dapper in your project. I've chosen this method, as it’s not only quicker, but will help build confidence with the CLI.

Cloning the Repo:

In your terminal app, navigate to the folder where you want the repository to be located and run the following command to clone the public tutorial repository:

git clone https://github.com/grant-dot-dev/dapper_tutorial.git

You then need to add Dapper and the Postgres support to your project. You can do this with the following command:

cd FCC.DapperTutorial

# add Dapper nuget package
dotnet add package Dapper

# add Postgres driver
dotnet add package Npgsql

Update your defaultConnection connection string within appsettings.json with the location of your Postgres instance.

"ConnectionStrings": {
  "DefaultConnection": "Host=localhost;Port=5432;Database=social_media;Username=postgres;Password=yourpassword"
}

Create Seeding File

Create a folder in your project called Infrastructure. Then within this folder create a file called DbUtilities.cs and populate it with the following code:

using Dapper;
using Npgsql;
using Microsoft.Extensions.Configuration;

namespace DapperTutorial.Infrastructure;

public static class DBUtilities
{
    private const string CreateUserSql = @"
        CREATE TABLE IF NOT EXISTS ""User"" (
            UserId TEXT PRIMARY KEY,
            Username TEXT,
            FirstName TEXT,
            LastName TEXT,
            Avatar TEXT,
            Email TEXT,
            DOB DATE
        );";

    private const string CreatePostSql = @"
        CREATE TABLE IF NOT EXISTS Post (
            PostId TEXT PRIMARY KEY,
            Likes INTEGER,
            Content TEXT,
            Timestamp TIMESTAMP,
            UserId TEXT,
            FOREIGN KEY(UserId) REFERENCES ""User""(UserId)
        );";

    private const string InsertUsersSql = @"
        INSERT INTO ""User"" (UserId, Username, FirstName, LastName, Avatar, Email, DOB) VALUES
            ('1', 'iron_man', 'Tony', 'Stark', NULL, 'tony.stark@example.com', '1970-05-29'),
            ('2', 'batman', 'Bruce', 'Wayne', NULL, 'bruce.wayne@example.com', '1972-11-11'),
            ('3', 'spiderman', 'Peter', 'Parker', NULL, 'peter.parker@example.com', '1995-08-10'),
            ('4', 'wonderwoman', 'Diana', 'Prince', NULL, 'diana.prince@example.com', '1985-04-02'),
            ('5', 'superman', 'Clark', 'Kent', NULL, 'clark.kent@example.com', '1980-07-18'),
            ('6', 'black-widow', 'Natasha', 'Romanoff', NULL, 'natasha.romanoff@example.com', '1983-06-25'),
            ('7', 'deadpool', 'Wade', 'Wilson', NULL, 'wade.wilson@example.com', '1977-02-19'),
            ('8', 'green-lantern', 'Hal', 'Jordan', NULL, 'hal.jordan@example.com', '1988-09-05'),
            ('9', 'captain-america', 'Steve', 'Rogers', NULL, 'steve.rogers@example.com', '1920-07-04'),
            ('10', 'catwoman', 'Selina', 'Kyle', NULL, 'selina.kyle@example.com', '1982-12-08')
        ON CONFLICT (UserId) DO NOTHING;";

    private const string InsertPostsSql = @"
        INSERT INTO Post (PostId, Likes, Content, Timestamp, UserId) VALUES
            ('p1', 10, 'Hello, world!', '2025-10-12 10:00:00', '1'),
            ('p2', 5, 'My first post!', '2025-10-12 11:00:00', '2'),
            ('p3', 7, 'Excited to join!', '2025-10-12 12:00:00', '3'),
            ('p4', 3, 'What a great day!', '2025-10-12 13:00:00', '4'),
            ('p5', 15, 'Superhero meetup!', '2025-10-12 14:00:00', '5')
        ON CONFLICT (PostId) DO NOTHING;";

    public static async Task SeedDatabaseAsync(IConfiguration configuration)
    {
        var connectionString = configuration.GetConnectionString("DefaultConnection");

        await using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync();

        await using var transaction = await connection.BeginTransactionAsync();

        try
        {
            await connection.ExecuteAsync(CreateUserSql, transaction: transaction);
            await connection.ExecuteAsync(CreatePostSql, transaction: transaction);

            var userCount = await connection.QuerySingleAsync<int>(
                @"SELECT COUNT(*) FROM ""User"";", transaction: transaction);

            var postCount = await connection.QuerySingleAsync<int>(
                "SELECT COUNT(*) FROM Post;", transaction: transaction);

            if (userCount > 0 && postCount > 0)
            {
                await transaction.CommitAsync();
                return;
            }

            await connection.ExecuteAsync(InsertUsersSql, transaction: transaction);
            await connection.ExecuteAsync(InsertPostsSql, transaction: transaction);

            await transaction.CommitAsync();
        }
        catch (Exception)
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}

Add a seed endpoint to your API within the Program.cs file as normal:

// add the using statement
using DapperTutorial.Infrastructure;

// add the seed endpoint
app.MapPost("/seed", async (IConfiguration configuration) =>
{
	try
	{
		await DBUtilities.SeedDatabaseAsync(configuration);
		return Results.Ok("Database seeded successfully.");
	}
	catch (Exception ex)
	{
		return Results.Problem($"An error occurred while seeding the database: {ex.Message}");
	}
});

In your terminal, run the app with dotnet run, call the /seed endpoint, and this will initiate the database. Check the records have seeded correctly by viewing your database within your preferred database tool.

Querying Data With Dapper

This section of the tutorial will focus on the basics of using the Dapper library to query the database. We'll explore the basics of loading, saving, and protecting your SQL database.

Firstly, we'll implement the repository pattern, a commonly used pattern in development. A repository acts as one stop place for all your database functions. Using a repository which implements an interface allows you to easily test and mock your database functionality.

Start by creating the models for your data within a Models folder.

// User.cs Model
public sealed class User
{
	public string UserId { get; init; } = string.Empty;
	public string Username { get; init; } = string.Empty;
	public string FirstName { get; init; } = string.Empty;
	public string LastName { get; init; } = string.Empty;
	public string? Avatar { get; init; }
	public string Email { get; init; } = string.Empty;
	public DateOnly? DOB { get; init; }
}

// Post.cs Model
public sealed class Post
{
	public string PostId { get; init; } = string.Empty;
	public int Likes { get; init; }
	public string Content { get; init; } = string.Empty;
	public DateTime Timestamp { get; init; }
	public string UserId { get; init; } = string.Empty;
}

Following best practice, create anIRepository.cs file within an Application folder, and paste the following code which defines your querying functions:

using DapperTutorial.Models;

namespace DapperTutorial.Application;

public interface IRepository
{
    Task<IReadOnlyList<User>> GetUsersAsync();
    Task<User?> GetUserByIdAsync(string userId);
    Task<IReadOnlyList<Post>> GetPostsAsync();
    Task<Post?> GetPostByIdAsync(string postId);
    Task<IReadOnlyList<Post>> GetPostsByUser(string userId);
}

You now have a base interface, which can be mocked for unit testing purposes within a wider application. This is one of the many perks of implementing this pattern.

Now, create the concrete implementation of the repository like so:

using Dapper;
using DapperTutorial.Application;
using DapperTutorial.Models;
using Npgsql;

namespace DapperTutorial.Infrastructure;

public class Repository(IConfiguration configuration) : IRepository
{
	private readonly string _connectionString = configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Missing connection string: ConnectionStrings:DefaultConnection");

	public async Task<IReadOnlyList<User>> GetUsersAsync()
	{
		const string sql = @"
			SELECT
				UserId,
				Username,
				FirstName,
				LastName,
				Avatar,
				Email,
				DOB
			FROM ""User""
			ORDER BY Username;";

		await using var connection = CreateConnection();
		var users = await connection.QueryAsync<User>(sql);
		return users.AsList();
	}

	public async Task<User?> GetUserByIdAsync(string userId)
	{
		const string sql = @"
			SELECT
				UserId,
				Username,
				FirstName,
				LastName,
				Avatar,
				Email,
				DOB
			FROM ""User""
			WHERE UserId = @UserId;";

		await using var connection = CreateConnection();
		return await connection.QuerySingleOrDefaultAsync<User>(sql, new { UserId = userId });
	}

	public async Task<IReadOnlyList<Post>> GetPostsAsync()
	{
		const string sql = @"
			SELECT
				PostId,
				Likes,
				Content,
				Timestamp,
				UserId
			FROM Post
			ORDER BY Timestamp DESC;";

		await using var connection = CreateConnection();
		var posts = await connection.QueryAsync<Post>(sql);
		return posts.AsList();
	}

	public async Task<IReadOnlyList<Post>> GetPostsByUser(string userId)
	{
		const string sql = @"
			SELECT
				PostId,
				Likes,
				Content,
				Timestamp,
				UserId
			FROM Post
			WHERE UserId = @UserId
			ORDER BY Timestamp DESC;";

		await using var connection = CreateConnection();
		var posts = await connection.QueryAsync<Post>(sql, new { UserId = userId });
		return posts.AsList();
	}

	public async Task<Post?> GetPostByIdAsync(string postId)
	{
		var sql = @"
			SELECT * FROM Post
			WHERE PostId = @PostId;";

		await using var connection = CreateConnection();
		return await connection.QuerySingleOrDefaultAsync<Post>(sql, new { PostId = postId });
	}

	private NpgsqlConnection CreateConnection() => new(_connectionString);
}

Unpacking the Repository:

The primary constructor injects IConfiguration, which is automatically provided by ASP.Net Core's built-in dependency injection system. This means you don't need to instantiate it yourself – the framework handles that for you. This allows access to the connection string defined in appsettings.json, which Dapper needs in order to connect to the database.

You'll also notice the null-coalescing ?? operator. This is a defensive pattern that throws a clear, descriptive error immediately if the connection string is missing, rather than letting a cryptic NullReferenceException surface somewhere further down the line when trying to use the connection string's value, if not provided.

Each method contains a SQL string prefixed with the @ character, making it a verbatim string literal. This allows the string to span multiple lines without needing \n escape characters or string concatenation, so we can format our SQL the same way we would in a query editor. This makes it much easier to read.

User is a reserved word in PostgreSQL, hence the double quotes around "User".

If you're familiar with SQL, you can easily use Dapper's query methods. This is the key strength of Dapper: it gives you the flexibility of plain SQL, with the added benefits of an ORM.

  • QueryAsync(): Use this method when your query could return zero or more results. It will always return a collection, never null, so you don't need to worry about null checks when no rows are found.

  • QuerySingleOrDefaultAsync(): Use this method when you expect at most one result. This is useful for methods like GetByIdAsync, or fetching a user's profile. If no matching row is found, it returns the default value for that type – null for reference types like User?.

QuerySingle vs QueryFirst

Both QuerySingleOrDefaultAsync and QueryFirstOrDefaultAsync are used when you want to return a single result, and both return the default value for the type of null for reference types, when no rows are found.

The difference is in how they behave when more than one row is returned.

QuerySingleOrDefaultAsync will throw an exception, which makes it the safer choice when querying by primary key, as if you ever get two results back, something has gone seriously wrong and you want to know about it immediately rather than silently returning the wrong record, such as incorrect duplicate.

QueryFirstOrDefaultAsync, on the other hand, takes the first result and ignores the rest. This makes it the better fit for scenarios such as "give me the most recent post by this user", where multiple results are expected yet you just want the top one, typically driven by an ORDER BY in your query.

Writing Data With Dapper

You have successfully created methods for querying and returning data from the repository, but you're going to require methods for inserting and updating existing records.

Insert Method

Add a CreateUserAsync() method definition to the IRepository interface, and then copy the below implementation into your Repository.cs:

public async Task<bool> CreateUserAsync(User user)
{
    const string sql = @"
        INSERT INTO ""User"" (
            UserId,
            Username,
            FirstName,
            LastName,
            Avatar,
            Email,
            DOB
        ) VALUES (
            @UserId,
            @Username,
            @FirstName,
            @LastName,
            @Avatar,
            @Email,
            @DOB
        );";

    await using var connection = CreateConnection();
    var rowsAffected = await connection.ExecuteAsync(sql, user);
    return rowsAffected > 0;
}

As before, state the SQL for inserting a record into the User table, with the parameters needed to assign the values.

A few things worth noting:

  • Dapper maps the user object's properties directly to the @ parameters by name, so you can just pass user rather than constructing an anonymous object.

  • ExecuteAsync() returns the number of rows affected, so returning rowsAffected > 0 gives the caller a simple success/failure bool.

  • Avatar and DOB are nullable on the model, and Dapper handles this gracefully: it will insert NULL into the database when those values are null.

Protecting Your Database – Parameterisation and SQL Injection

You may have noticed that throughout this tutorial, values are never inserted directly into the SQL string. Instead, we use placeholders like @UserId and @Username, with the actual values passed separately. This is called parameterisation, and it's one of the most important habits you can build as a developer.

To understand why, consider what happens without it. If you were to build a query using string interpolation:

// Never do this
var sql = $"SELECT * FROM \"User\" WHERE Username = '{username}'";


//A malicious user could pass the following as their username:
```
' OR '1'='1

Which would turn your query into:

SELECT * FROM "User" WHERE Username = '' OR '1'='1'

Since '1'='1' is always true, this query returns every user in the database. With a more destructive payload, an attacker could drop tables, delete records, or extract sensitive data entirely. This is known as a SQL injection attack, and it remains one of the most common and damaging vulnerabilities in web applications.

Dapper protects you from this automatically. When you write:

const string sql = @"SELECT * FROM ""User"" WHERE Username = @Username";
var user = await connection.QuerySingleOrDefaultAsync<User>(sql, new { Username = username });

Dapper sends the SQL and the values to the database separately. The database receives the query structure first, compiles it, and then applies the value as pure data – it can never be interpreted as SQL. No matter what a user passes in, it will always be treated as a value, never as an instruction.

This is why throughout this tutorial, you will always see:

  • @ParameterName placeholders in the SQL string

  • Values passed as an anonymous object, a model, or DynamicParameters

But never string interpolation or concatenation inside a SQL string.

Updating Data With Dapper

You don't always want to create new records. Sometimes, you would rather update records instead. Below is the code required to do this. Add this method to your repository, not forgetting to add the definition to the repository interface:

public async Task<bool> UpdateUserAsync(User user)
	{
        const string sql = @"
        UPDATE ""User"" SET
            Username = @Username,
            FirstName = @FirstName,
            LastName = @LastName,
            Avatar = @Avatar,
            Email = @Email,
            DOB = @DOB
        WHERE UserId = @UserId;";

		await using var connection = CreateConnection();
		var affectedRows = await connection.ExecuteAsync(sql, user);
		return affectedRows > 0;
	}

Like all other queries / actions with Dapper, an SQL command is written and passed to ExecuteAsync() along with the object to be updated, and Dapper will automatically map the properties based on name.

Deleting Records With Dapper

Just like before, add the following code to your repository. Add your delete SQL command, and pass to the ExecuteAsync() method. Below, instead of passing the user object, you're manually mapping the userId to the SQL `@UserId` parameter.

public async Task<bool> DeleteUserAsync(string userId)
	{
		const string sql = @"
			DELETE FROM ""User""
			WHERE UserId = @UserId;";

		await using var connection = CreateConnection();
		var affectedRows = await connection.ExecuteAsync(sql, new { UserId = userId });
		return affectedRows > 0;
	}

There may be a time where you don't want to delete one record at a time, and instead would like to delete multiple in one go. This can be achieved by Dapper's internal mechanics, and passing a list of items to the ExecuteAsync() method, like so:

public async Task<bool> DeleteUsersBatchAsync(IEnumerable<string> userIds)
{
    const string sql = @"
        DELETE FROM ""User""
        WHERE UserId = @UserId;";

    await using var connection = CreateConnection();
    var affectedRows = await connection.ExecuteAsync(sql, userIds.Select(id => new { UserId = id }));

    return affectedRows > 0;
}

Dapper will iterate over the collection and execute the statement once per item. It's not a single batch query under the hood it's multiple executions, so for large datasets you'd want to consider a different approach – but for typical use cases it's clean and convenient.

Batch Processing With Dapper

For true batch processing in a single round trip, you would need to lean on SQL rather than Dapper itself. Dapper is just the messenger here, and the batching logic lives in the query.

The most common approach is using In / Any (depending on SQL database flavour):

public async Task<bool> DeleteUsersAsync(IEnumerable<string> userIds)
{
    const string sql = @"
        DELETE FROM ""User""
        WHERE UserId = ANY(@UserIds);";

    await using var connection = CreateConnection();
    var affectedRows = await connection.ExecuteAsync(sql, new { UserIds = userIds.ToArray() });
    return affectedRows > 0;
}

This sends a single query to the database with all the IDs, rather than one query per ID. Npgsql supports passing an array directly to ANY(), which is the PostgreSQL idiom for this.

Note: ANY(@UserIds) is PostgreSQL-specific syntax. If you were using SQL Server you'd use WHERE UserId IN @UserIds instead – Dapper has built-in support for expanding IN parameters when passed an IEnumerable.

In summary:

  • Multiple executions: one round trip per item, simple but inefficient at scale, especially when deleting a lot of users, or their posts.

  • ANY / IN: single round trip regardless of list size, much more efficient for larger datasets.

For most everyday use cases, the difference won't matter. But it's a good habit to reach for the single query approach when you know you're dealing with a collection.

💡
Remember to keep the IRepository interface updated with any new methods you've learnt within this tutorial so far

Transactions With Dapper

If you cast your mind back to the seeding utility, you may have noticed a transaction was already being used there. Now it's time to understand what they are and why they matter.

A transaction guarantees that either all operations succeed, or none of them do. This is the atomicity principle: the database will never be left in a half-finished state.

Imagine the scenario where a new user signs up to the social media application, and you ask them to write their first post, during the registration process – meaning their account and their first post is stored in the same operation.

Without a transaction, if the user insert succeeds but the post insert fails, you're left with an orphaned user record in the database – an account with no post, and no way to know something went wrong.

If you're familiar with SQL, you may have written transactions directly like so:

BEGIN TRANSACTION;
    INSERT INTO "User" (UserId, Username ...) VALUES (...);
    INSERT INTO Post (PostId, ...) VALUES (...);
COMMIT;

With Dapper, rather than writing the transaction logic inside the SQL itself, you manage it from your C# code instead. Each SQL statement stays as its own focused block, and you wrap them together in a transaction at the application level. This gives you the same atomicity guarantee, but with the added benefit of being able to use C#'s try/catch to handle failures and trigger a rollback cleanly.

public async Task<bool> CreateUserWithPostAsync(User user, Post post)
{
    const string insertUserSql = @"
        INSERT INTO ""User"" (UserId, Username, FirstName, LastName, Avatar, Email, DOB)
        VALUES (@UserId, @Username, @FirstName, @LastName, @Avatar, @Email, @DOB);";

    const string insertPostSql = @"
        INSERT INTO Post (PostId, Likes, Content, Timestamp, UserId)
        VALUES (@PostId, @Likes, @Content, @Timestamp, @UserId);";

    await using var connection = CreateConnection();
    await connection.OpenAsync();
    await using var transaction = await connection.BeginTransactionAsync();

    try
    {
        await connection.ExecuteAsync(insertUserSql, user, transaction);
        await connection.ExecuteAsync(insertPostSql, post, transaction);
        await transaction.CommitAsync();
        return true;
    }
    catch (Exception)
    {
        await transaction.RollbackAsync();
        return false;
    }
}

It also means you can write some useful methods, which run particular SQL blocks conditionally. Take the below example: there are 3 different SQL blocks which can be run as part of the transaction insertUserSql, insertPostSql and logActivitySql.

For the below example, imagine you have an ActivityLog table that tracks user actions across the application. All three blocks are part of the same transaction, but only two of them are guaranteed to run. If the flag is inactive, the activity log is skipped entirely, and the transaction commits with just the user and post. If anything fails, all of it rolls back regardless.

public async Task<bool> CreateUserWithPostAndLogAsync(User user, Post post, bool logActivity)
{
    const string insertUserSql = @"
        INSERT INTO ""User"" (UserId, Username, FirstName, LastName, Avatar, Email, DOB)
        VALUES (@UserId, @Username, @FirstName, @LastName, @Avatar, @Email, @DOB);";

    const string insertPostSql = @"
        INSERT INTO Post (PostId, Likes, Content, Timestamp, UserId)
        VALUES (@PostId, @Likes, @Content, @Timestamp, @UserId);";

    const string logActivitySql = @"
        INSERT INTO ActivityLog (UserId, Action, Timestamp)
        VALUES (@UserId, 'SIGNUP', @Timestamp);";

    await using var connection = CreateConnection();
    await connection.OpenAsync();
    await using var transaction = await connection.BeginTransactionAsync();

    try
    {
        await connection.ExecuteAsync(insertUserSql, user, transaction);
        await connection.ExecuteAsync(insertPostSql, post, transaction);

        if (logActivity)
        {
            await connection.ExecuteAsync(logActivitySql, new { user.UserId, Timestamp = DateTime.UtcNow }, transaction);
        }

        await transaction.CommitAsync();
        return true;
    }
    catch (Exception)
    {
        await transaction.RollbackAsync();
        return false;
    }
}

Multi-mapping / Splits

So far, every query has returned data from a single table, mapped to a single object. But real applications rarely work that way. Data is relational, and you'll frequently need to query across multiple tables at once using a JOIN.

Dapper handles this through multi-mapping, which allows a single query to map results across multiple objects simultaneously. To demonstrate this, you're going to return a list of posts with their associated author's details populated (rather than just a UserId foreign key reference).

First, create the model used to store the combined data in your Models folder.

public sealed class PostWithAuthor
{
    public Post Post { get; init; } = null!;
    public User Author { get; init; } = null!;
}

Now add the following method to your repository (not forgetting to always add the method definition to your interface too).

public async Task<IReadOnlyList<PostWithAuthor>> GetPostsWithAuthorsAsync()
{
    const string sql = @"
        SELECT
            p.PostId,
            p.Likes,
            p.Content,
            p.Timestamp,
            p.UserId,
            u.UserId,
            u.Username,
            u.FirstName,
            u.LastName,
            u.Avatar,
            u.Email,
            u.DOB
        FROM Post p
        INNER JOIN ""User"" u ON p.UserId = u.UserId
        ORDER BY p.Timestamp DESC;";

    await using var connection = CreateConnection();

    var results = await connection.QueryAsync<Post, User, PostWithAuthor>(
        sql,
        (post, user) => new PostWithAuthor { Post = post, Author = user },
        splitOn: "UserId"
    );

    return results.AsList();
}

There is quite a bit going on here compared to the queries you have written so far, so let's unpack it.

SELECT Statement

Unlike previous queries where you could rely on SELECT *, column order matters here. Dapper needs to know where the Post columns end and the User columns begin in the result set. You're explicitly listing every column, with all Post columns first and all User columns second.

QueryAsync<Post, User, PostWithAuthor>

Rather than a single generic type, you are now passing three: the first two are the objects you want to map to, and the third is the return type. Dapper will split the result set into a Post and a User, and hand both to you to combine however you like.

The Mapping Function

(post, user) => new PostWithAuthor { Post = post, Author = user }

This is where you stitch the two objects together. Dapper passes you the mapped Post and User for each row, and you return the combined PostWithAuthor. This is also where you have full control: if you wanted to flatten the result into a single object rather than a nested one, you could do that here instead.

splitOn: "UserId"

This is the most important part to understand. The splitOn parameter tells Dapper the name of the column where the first object ends and the second begins. In this case, when Dapper encounters UserId for the second time in the result set, it knows everything from that point onwards belongs to the User object.

It defaults to Id – so if your split column were named Id, you wouldn't need to specify it at all. Since both Post and User share UserId as their identifier, you must specify it explicitly here.

A Common Mistake

Because splitOn works by column position, the column you split on must be the first column of the second object in your SELECT. If u.UserId were not the first User column listed, Dapper would split in the wrong place and you'd end up with incorrectly mapped data. It's the kind of bug that can be tricky to spot because no exception is thrown, the data is just wrong.

What Comes Next – Possible Additions

Use this tutorial as a base to build upon. The next steps are to look at how you could structure your project to make your repository tidier. For example, you could move your SQL commands into files of their own, and import them in to your repository, making your repository code slimmer.

To avoid this tutorial becoming too bloated, I tried to cover the key features of Dapper. Now you know the fundamentals, you could push your learning further to cover:

  • QueryMultiple: execute several SQL statements in a single round trip. This is ideal for dashboard-style endpoints.

  • Dynamic parameters: build queries conditionally using DynamicParameters, which is useful for search and filter scenarios.

  • Endpoint implementation: add API endpoints to call your repository methods, injecting the IRepository into your minimal endpoints.

    For example:

app.MapPost("/insert-user", async (IRepository repository, User newUser) =>
{
	var success = await repository.InsertUserAsync(newUser);
	return success
		? Results.Ok($"User {newUser.Username} inserted successfully.")
		: Results.Problem("Failed to insert user.");
});

Final Thoughts

Dapper won't be the right tool for every project. If you're working with complex entity relationships, frequent schema changes, or a team heavily invested in LINQ, Entity Framework may be the better fit. And there's nothing wrong with that.

But when performance matters, when you need precise control over what's hitting your database, or when you're working with a schema you don't own, Dapper's transparency is hard to beat. You always know exactly what query is being executed, because you wrote it yourself.

The trade-off is straightforward: Entity Framework gives you speed of development, Dapper gives you speed of execution. Understanding both makes you a more well-rounded .NET developer – and knowing when to reach for each one is a skill in itself.

If you already know SQL, Dapper's learning curve is remarkably shallow. There's no magic, no conventions to memorise, no generated queries to second-guess. Just SQL, mapped cleanly to your C# objects, and as this tutorial has hopefully shown, that doesn't have to mean unsafe or hard to maintain.

Again, thanks for reading and I hope you have found this tutorial useful. If you'd like to chat about anything in this tutorial or hear about future articles, you can follow me on X/Twitter.



Read the whole story
alvinashcraft
37 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

An Introduction to Database System Design

1 Share

These days, businesses and startups rely on well-designed databases to manage vast amounts of data. In domains like Healthcare, E-commerce, and Fintech/Banking, a solid database design ensures data integrity, security, and accessibility.

In this article, we'll talk about what it takes to design a highly-functional database using some key best practices.

This article is aimed at developers and those looking to start a career in managing Databases. We'll discuss what a database actually is, the components of a Database System, what we mean by database design, the stages of database design, and what's involved in Database System Design.

Table of Contents

  1. Prerequisites and Setup

  2. What is a Database?

  3. Components of a Database System

  4. Types of Database Systems

  5. Database System vs. DBMS

  6. Characteristics of a Good Database

  7. Stages of Database Design

  8. The Role of Normalisation

  9. Practical: Designing a Library System

  10. Conclusion

Prerequisites and Setup

To get the most out of this guide, you should have the following foundational skills and tools ready. This will help ensure that you aren't just reading theory, but that you're actually building a functional system.

1. Foundational Knowledge

  • Data Types: You should be able to distinguish between basic data formats. In database design, choosing the wrong type can lead to storage waste or application errors.

    • Strings/Varchars: Textual data (for example, "John Doe", "123 Main St").

    • Integers: Whole numbers used for math or unique IDs (for example, 10, 500).

    • Floats/Decimals: Numbers with decimal points, usually for currency (for example, 19.99).

    • Booleans: Simple True/False toggles (for example, is_available).

  • Logical Thinking: You should be comfortable identifying "entities." If you're building an app for a school, you'll need to recognize that "Students," "Teachers," and "Classrooms" are separate objects that must be linked via relationships.

  • Terminal/CLI Basics: While we'll use visual tools, you should know how to open your Command Prompt (Windows) or Terminal (Mac/Linux) and understand that commands are often case-sensitive.

2. Software and Installation

We'll use PostgreSQL (the database engine) and pgAdmin 4 (the visual management tool).

  1. Download: Visit the official PostgreSQL Downloads page and select the installer for your operating system.

  2. Installation Wizard: Run the installer. When asked which components to include, make sure that PostgreSQL Server, pgAdmin 4, and Command Line Tools are all checked.

  3. The "Postgres" User: During setup, you will be prompted to create a password for the default "postgres" superuser. Note: Write this password down. You can't easily reset it, and you'll need it to access your data.

  4. Port Selection: The default port is 5432. Keep this as the default unless you're an advanced user with a specific reason to change it.

3. Verifying Your Setup

Before moving to the practical section, let's verify that everything is installed correctly:

  1. Open pgAdmin 4 from your applications menu.

  2. In the left-hand sidebar, click on Servers.

  3. Enter the master password you created during installation.

  4. If you see "PostgreSQL [Version Number]" appear with a green icon, your database environment is successfully configured.

What is a Database?

A Database is a collection of structured data usually stored electronically in a computer. Databases are controlled and managed using a Database Management System (DBMS). Database Management Software is an application that constructs and maintain (and sometimes expands) Databases. Examples of DBMS are IBM's DB2, Oracle Corporation's Oracle, Microsoft Access, and Microsoft's SQL Server.

We use databases everyday, whether knowingly or unknowingly. And as a developer, you'll likely need to at least understand Database basics so you can effectively work with them.

It's also important for you to how to know how to design a scalable database, as well as be familiar with the environment in which the database will be housed (called, not surprisingly, a Database Environment). The hardware and operating system that house the database makes up this Database Environment.

Components of a Database System

A Database System is a computerized record-keeping system designed to store, manage, and retrieve data efficiently. It acts as a centralized repository that allows multiple users to access and manipulate data simultaneously while ensuring the integrity, security, and persistence of that information over time.

A Database System consists of four basic components:

1. Hardware

This includes the secondary storage where the database resides alongside other necessary components. Examples are Hard disks, Processors, RAM, and so on. Since a database can span from a single workstation to a global mainframe, hardware selection is a priority. Proper investment in processing power and storage is essential to handling the projected user load and data volume.

2. Software

In this case, Database Management Software (DBMS) is in charge of the maintenance and management of databases. It's robust software that acts as an intermediary, restricting users from the complex hardware-level details of data storage. The Software layer (DBMS) handles data storage, retrieval, and processing. Examples of DBMS are Microsoft's SQL Server, IBM's DB2, and Oracle.

3. Data

Data serves as the bridge connecting the machine components (hardware and software) to the human users. In a database system, data is organized into two main types:

  • User Data: The actual structured information stored in tables, made up of columns (attributes) and rows (records).

  • Metadata: Often defined as "data about data," metadata is stored in system tables and describes the actual structure of the database, such as the number of tables, field names, and defined primary keys

4. Users

These are the people who interact with the database to carry out their business responsibilities. Users generally fall into three distinct categories:

  • Database Administrators (DBAs): Technical experts who hold central responsibility for the database. They monitor performance, define security and integrity checks, and establish backup and recovery strategies.

  • Database Designers/Programmers: The engineers who actually write the code and use the DBMS to create the database's logical structure.

  • End Users: The everyday people who access the database using query languages or simple menu-driven application interfaces

Types of Database Systems

It's important to know that not all databases store data in the same way. The choice of database depends on the specific needs of the application. The primary types include:

Hierarchical and Network Databases

These are older, legacy models. Hierarchical databases structure data in a tree-like, parent-child format where a child can only have one parent. Network databases improved on this by allowing a graph-like structure where records can have multiple parent and child relationships, making it easier to model complex associations.

Relational Databases (RDBMS)

The most widely used type today. They organize data into structured tables consisting of rows and columns. These tables are linked using primary and foreign keys, and they use Structured Query Language (SQL) for operations. They are ideal for applications requiring strong consistency, like banking systems.

Object-Oriented Databases (OODBMS)

These combine database capabilities with object-oriented programming principles (like Java or C++). Data is stored as "objects" that contain both the data and the methods (functions) that operate on it, making them great for complex data like multimedia or engineering designs.

NoSQL Databases

Designed to handle large volumes of unstructured or semi-structured data. Unlike relational databases, they don't rely on rigid table structures and are highly scalable. Types of NoSQL include document stores (for example, MongoDB), key-value stores (for example, Redis), column-family stores, and graph databases.

Cloud and Distributed Databases

Cloud databases are hosted on cloud platforms (like AWS or Microsoft Azure) and offer elasticity, scalability, and cost-efficiency (pay-as-you-go).

Distributed databases store data across multiple physical locations but function as a single unified system to the user, providing high availability and fault tolerance.

Database System vs. Database Management System (DBMS)

People often use "Database" and "DBMS" interchangeably, but there is a distinct difference:

  • Database Management System (DBMS): This is purely the software that helps users interact with the database. It handles data storage, retrieval, security, and concurrency control. Examples include MySQL, PostgreSQL, and Oracle DB.

  • Database System: This is the broader concept that encompasses the entire setup. It includes the actual database (where data is stored), the DBMS software, the physical hardware, the network, and the users interacting with it.

Characteristics of a Good Database

To make sure that your database design is successful, it should exhibit several core characteristics:

  • Data integrity and consistency: Ensuring data is accurate, reliable, and uniform across the entire system.

  • Data security: Protecting sensitive information from unauthorized access and potential breaches.

  • Scalability and performance: The ability to handle increasing amounts of data and users efficiently while providing fast query processing.

  • Redundancy management (normalization): Avoiding unnecessary duplication of data to save storage space and prevent errors during updates.

  • Concurrency control: Allowing multiple users to access and modify data simultaneously without causing conflicts or data corruption.

  • Backup and recovery: Supporting robust mechanisms to recover data in the event of hardware or system failures.

Stages of Database Design

Database design is a structured process consisting of several stages to ensure that data is efficiently stored, accessed, and managed. There are four key stages in this process:

Requirements Analysis

This is the foundational stage where designers gather and analyse the specific needs of users and the business. It involves identifying the overall purpose of the database, understanding data requirements, defining key entities and attributes, and establishing both functional and non-functional requirements.

Conceptual Design

In this phase, a high-level visual blueprint of the database is created, which is independent of any specific software implementation. This makes it easy for non-technical stakeholders to understand.

Designers typically use Entity-Relationship (ER) models or UML diagrams to identify entities, map out relationships, and define constraints like primary keys.

Logical Design

This stage involves translating the conceptual blueprint into a logical model that aligns with a specific type of Database Management System (DBMS), such as a relational or NoSQL system.

Key steps include converting the ER diagram into relational schemas (tables and columns), defining foreign and primary keys, and normalising the database to remove anomalies and reduce data redundancy.

Physical Design

The final stage translates the logical model into an actual physical structure optimized for high performance and efficient storage. Activities here include selecting the specific DBMS, establishing indexing strategies to speed up data retrieval, defining access paths, and configuring essential security policies and backup mechanisms.

The Role of Normalisation in Database Design

When building a relational database, one of the most critical steps in the logical design phase is a process called Normalisation.

Normalisation is a systematic approach to organising data to minimise redundancy (duplicate data) and improve overall data integrity. Essentially, it involves taking large, clunky tables and decomposing them into smaller, more focused tables, then linking them together using defined relationships.

Why is Normalisation Important?

A poorly designed database often suffers from errors that happen when you try to insert, update, or delete data. For example, if a teacher's phone number is stored in multiple places, updating it in one row but forgetting another creates an update anomaly. Normalisation solves this by ensuring each piece of information is stored in only one place.

The main objectives of normalisation are:

  • Eliminating redundancy: By reducing duplicate data, you save valuable storage space and keep your data consistent.

  • Avoiding anomalies: It prevents data corruption that arises during insertion, updating, or deletion.

  • Ensuring data integrity: It maintains the accuracy and reliability of the data across the entire database.

  • Enhancing query performance: Organising the data efficiently helps optimise how data is retrieved and updated.

Stages of Normalisation

Normalisation happens in sequential stages known as Normal Forms (NFs), where each stage builds upon the rules of the previous one to further refine the database structure.

For beginners, the first three forms are the most important to understand:

  • First Normal Form (1NF): This stage ensures "atomicity". This means that every column in a table should hold a single, indivisible value, and duplicate columns are eliminated. For instance, you would not store two different phone numbers in a single "Phone" cell; you would separate them.

  • Second Normal Form (2NF): To achieve 2NF, the table must first be in 1NF. Then, it ensures that all non-key attributes (the regular data columns) are fully dependent on the entire primary key. This often involves creating separate tables for distinct entities, like putting "Courses" into their own table rather than mixing them with "Student" details.

  • Third Normal Form (3NF): A table in 3NF is already in 2NF and has removed all "transitive dependencies". This means that a non-key column shouldn't depend on another non-key column. For example, if a table has an "Instructor Name" and "Instructor Phone," those details should live in a dedicated "Instructor" table, not inside a "Course" table.

  • Boyce-Codd Normal Form (BCNF): This is a stricter version of 3NF used to resolve any remaining, complex anomalies.

Finding the Right Balance

While normalisation is crucial for maintaining data consistency, it's important to strike a balance. Minimising redundancy is great, but excessive normalisation creates dozens of tiny tables. When you need to retrieve a complete record, the database system has to piece all those tables back together (using complex queries called "joins"), which can slow down performance.

So the ultimate goal of a good database designer is to find the sweet spot between a highly normalised structure and efficient query performance.

Practical: Designing a Library System

To move from theory to practice, let’s build a database for a small local library. We'll go through the design stages to ensure the data is structured efficiently.

Step 1: Requirements & ER Diagram

First, we'll identify what our library needs to track. We have three main entities:

  • Authors: The writers of the books.

  • Books: The actual items available for loan.

  • Members: The people who borrow the books.

The Relationships:

  • One Author can write many Books (One-to-Many).

  • One Member can borrow many Books (One-to-Many).

Here's the ER diagram I've created for this example:

ER diagram

Step 2: Normalization in Action

To ensure our database is "well-designed" and free of redundancy, we'll apply the normalization rules discussed earlier. Instead of one giant spreadsheet, we split the data into three distinct tables:

  1. Authors Table: * author_id (Primary Key)

    • author_name
  2. Books Table: * book_id (Primary Key)

    • title

    • isbn

    • author_id (Foreign Key linking to the Authors table)

  3. Members Table: * member_id (Primary Key)

    • first_name

    • last_name

    • email (Unique constraint)

Step 3: Implementation (SQL)

Now, let’s use the PostgreSQL Query Tool in pgAdmin 4 to actually create these tables and insert some dummy data.

-- 1. Create the Authors table
CREATE TABLE Authors (
    author_id SERIAL PRIMARY KEY,
    author_name VARCHAR(100) NOT NULL
);

-- 2. Create the Books table with a relationship to Authors
CREATE TABLE Books (
    book_id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    isbn VARCHAR(20) UNIQUE,
    author_id INT REFERENCES Authors(author_id)
);

-- 3. Create the Members table
CREATE TABLE Members (
    member_id SERIAL PRIMARY KEY,
    first_name VARCHAR(50),
    last_name VARCHAR(50),
    email VARCHAR(100) UNIQUE NOT NULL
);

-- 4. Insert dummy data to test the design
INSERT INTO Authors (author_name) 
VALUES ('J.R.R. Tolkien'), ('George R.R. Martin');

INSERT INTO Books (title, isbn, author_id) 
VALUES ('The Hobbit', '978-0261102217', 1), 
       ('A Game of Thrones', '978-0553103540', 2);

Understanding the Schema Design:

By running the SQL script above, you’ve successfully transitioned from a logical design to a physical database. Here is a breakdown of the key concepts we applied:

  • Primary Keys (PK): Using SERIAL PRIMARY KEY automatically creates a unique, incrementing ID for every new entry. This ensures no two authors or books are ever confused by the system.

  • Foreign Keys (FK): The REFERENCES Authors(author_id) command is where the "Relational" part of a Relational Database happens. It tells the Books table that it must point to a valid ID in the Authors table, preventing "orphan" books without creators.

  • Constraints: By using UNIQUE on the isbn and email columns, we've programmed the database to reject any duplicate data, ensuring high data integrity.

How to Fetch Your Data

Now that the data is stored, you need to know how to get it back out. In SQL, we can do this using the SELECT statement.

1. See Everything in a Table

To see all books currently in the library:

SELECT * FROM Books;

2. Filtering Results

Often, you don't want every single row. You can use the WHERE clause to filter for specific data. For example, to find an author by their exact name:

SELECT * FROM Authors 
WHERE author_name = 'J.R.R. Tolkien';

3. Joining Tables

In a normalized database, information is spread across tables. To see a list of book titles alongside their actual author names (instead of just an ID number), you use a JOIN.

SELECT Books.title, Authors.author_name
FROM Books
JOIN Authors ON Books.author_id = Authors.author_id;

The database is instructed by this query to "give the titles from the Books table and the names from the Authors table where the author_id matches in both." This enables you to effectively store data in distinct tables while viewing it as a single, comprehensive report.

This ability to link information across tables is what makes Relational Databases the industry standard for most business applications. But while the relational model is powerful, it isn't the only way to store data. Depending on whether you're handling social media connections, real-time sensor data, or simple document storage, you might need a different architectural approach.

Conclusion

Designing a database is much more than simply throwing information into a computer. It's the process of building a robust, efficient, and secure foundation for decision-making and business operations.

As we have explored here, a successful database relies on a carefully orchestrated ecosystem of hardware, software (the DBMS), data, and users.

By following the four stages of design – Requirements Analysis, Conceptual Design, Logical Design, and Physical Design – you can avoid costly structural mistakes and ensure that your system perfectly aligns with user needs.

Applying critical techniques like normalisation during this process guarantees that your data remains consistent, accurate, and free from frustrating anomalies.

Also, as the digital landscape continues to evolve, mastering these foundational concepts is your stepping stone into the future. Traditional relational databases remain incredibly powerful, but modern data demands are rapidly driving the adoption of cloud-based, AI-powered, and serverless database systems.

A well-designed system today must not only focus on data integrity and query performance but also prioritise scalability and stringent data security to protect against modern cyber threats.

Whether you are building a simple address book or architecting a backend for the next big application, keeping these core principles of database system design in mind will empower you to create resilient, high-performing, and future-proof data solutions.



Read the whole story
alvinashcraft
43 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

The Claude Code Handbook: A Professional Introduction to Building with AI-Assisted Development

1 Share

"I have never enjoyed coding as much as I do today — because I no longer have to deal with the minutia."Boris Cherny, Head of Claude Code, Anthropic

This handbook is a complete, professional introduction to Claude Code, Anthropic's AI-powered software development agent – and to the practice of building software with it.

Claude Code isn't a smarter autocomplete. It's an agent: a system that reads your codebase, reasons about what needs to be done, writes and edits files, runs commands, and works through a task from start to finish – with you directing it, verifying its output, and making the decisions that require judgment. It represents a meaningful shift in how software gets built, not an incremental improvement on what came before.

This handbook covers everything from installation and first sessions to parallel agent workflows, MCP integrations, and autonomous loops. It's organized to build competency progressively, as each chapter assumes you've read the previous ones. But it's also written to be a reference you return to as your practice develops. The goal is not to make you familiar with Claude Code. It's to make you capable with it.

What You Will Learn

By the end of this handbook, you'll be able to do things that previously required either years of engineering experience or a team:

  • Build real applications from scratch – not toy projects or tutorial reproductions, but working software you intend to deploy and use

  • Stop waiting on developers – take ideas from concept to working prototype yourself, without depending on someone else's availability or budget

  • Ship features in hours instead of weeks – structure sessions with Plan Mode, feature-by-feature building, and prompt discipline so Claude produces what you actually intended

  • Keep projects alive across dozens of sessions – manage context windows and maintain continuity so you never lose progress or have to reconstruct context from scratch

  • Connect your tools – link Claude Code to GitHub, Notion, Slack, Google Workspace, and other services via the Model Context Protocol (MCP) so you execute entire workflows from a single instruction

  • Work like a team of one – run parallel agent workflows that produce the output of three or four engineers running simultaneously, without coordination overhead

  • Avoid the mistakes that waste days – understand when and how to use autonomous loops safely, so you don't return to a session to find Claude has gone in the wrong direction for two hours

  • Produce code you can stand behind – review and verify AI-generated output to a professional standard, so nothing ships that you do not actually understand and own

If you have wanted to build software and kept hitting the same wall, this handbook is designed to remove it.

Who This Is For

This handbook was written for anyone who intends to work with Claude Code seriously. The audience is broader than it might initially appear – the access barrier to software development is changing, and so is the definition of who should be able to build.

1. Developers who want to operate at a different scale

If you've been writing software for years, Claude Code is not a replacement for your skills – it's a multiplier. The developers getting the most from it aren't using it as an autocomplete tool. They're using it to run parallel sessions, delegate entire feature workstreams, maintain codebases that would have required a team, and ship at a rate that was previously impossible without additional headcount.

This handbook covers the practices – Plan Mode, context management, autonomous loops, Git worktrees – that separate professional-level Claude Code use from basic use.

2. Founders, product people, and domain experts who want to remove one specific blocker

Something important to understand before you read further: writing the code is a small fraction of what actually has to go right for a product to succeed. A working codebase doesn't produce users, doesn't validate a business model, doesn't guarantee product-market fit, and doesn't substitute for the judgment, distribution, and domain knowledge that determine whether software is actually useful to anyone.

Claude Code removes the coding barrier. That barrier was previously significant: for non-technical people, it was often the thing that made building impossible to begin. Removing it matters. But it's not the same as removing every other obstacle between you and a product that works in the world.

What does remain yours, fully, includes: understanding the problem you're solving, determining whether the solution is actually the right one, deciding what to build and in what order, talking to users, understanding why people would or would not use what you're building, and everything involved in getting it in front of the people it's meant for.

This handbook will get you from concept to working software. The rest – the part that actually makes that software valuable – is not a technical problem.

3. Anyone who has an idea they have been unable to act on

If the thing that has blocked you is specifically the code – not the idea, not the market, not the distribution, but the code itself – then this handbook is for you. It removes that specific obstacle.

It won't remove the others. The expectation going in should be accurate: this gives you a working application faster than was previously possible, not a shortcut to a successful product.

No prior programming experience is required to begin – but prior experience will make the journey faster. What is required in either case is the willingness to engage seriously with what Claude Code produces: to read it, question it, verify it, and direct it toward what you actually need.

Why This Skill Matters

People are drawn to Claude Code and AI-assisted development because it unlocks opportunities that were previously unavailable. The ability to build software – to take an idea from concept to working product – has historically required years of training, a funded team, or both. That barrier is changing rapidly.

This is not a tool that will make human judgment irrelevant. AI will automate a great deal, but not everything. The world is too interconnected, too complex, and too dependent on human creativity, domain expertise, and judgment for complete automation to be possible.

There are things beyond writing code that keep the world functioning, and that will remain true. But for the act of building software – bringing your own ideas to life as working applications – Claude Code is genuinely transformative.

As of early 2026, Claude Code authors 4% of all global GitHub commits. Engineers at Spotify have not written code manually since December. Anthropic's own team ships 10–30 pull requests per day per engineer, every one generated by Claude. This is the current state – not a projection.

The skill of directing, evaluating, and building with AI tools is already one of the most valuable capabilities a professional can hold. That value will increase, not decrease, as AI becomes more capable. The developers, builders, and thinkers who build fluency with tools like Claude Code now will carry a structural advantage as the field advances.

Table of Contents

  1. Chapter 1: The Context That Made Claude Code Necessary

  2. Chapter 2: Anthropic — Background and Purpose

  3. Chapter 3: The Claude Model Family

  4. Chapter 4: What Claude Code Is

  5. Chapter 5: Why the Development Community Required This

  6. Chapter 6: Installation and Initial Setup

  7. Chapter 7: VS Code and the Claude Code Extension

  8. Chapter 8: Subscriptions, Token Costs, and Usage

  9. Chapter 9: Working in Your First Session

  10. Chapter 10: Prompt Discipline — Inputs Determine Outputs

  11. Chapter 11: Planning as a Core Practice

  12. Chapter 12: Building Feature by Feature

  13. Chapter 13: How Claude Code Actually Works

  14. Chapter 14: Architecting Applications Well with Claude Code

  15. Chapter 15: Plan Mode, Edit Mode, and Operational Modes

  16. Chapter 16: Context Windows and Session Management

  17. Chapter 17: MCP Servers and External Integrations

  18. Chapter 18: Agents, Sub-Agents, and Parallel Workflows

  19. Chapter 19: Skills, Rules, and Persistent Instructions

  20. Chapter 20: Autonomous Loops — Conditions for Use

  21. Chapter 21: Code Review, Security, and Verification

  22. Chapter 22: Starter Project Blueprints

  23. Chapter 23: The Current Frontier of Claude Code

  24. Chapter 24: Software Engineering as a Discipline

  25. Chapter 25: A Structured Path Forward

Chapter 1: The Context That Made Claude Code Necessary

Chapter 1 Header

Software development has historically been access-restricted. Building a working application like a web service, a data tool, or a user-facing product required either years of technical training or a funded team of engineers. The knowledge barrier was steep, the required time investment was significant, and the population of people who could build was correspondingly small.

This constraint began to erode with the emergence of large language models capable of generating functional code from natural-language descriptions. What started as an augmentation of individual developers has, within the span of a few years, become a structural transformation of how software is built.

As of early 2026, the scale of this shift is measurable and substantial, and its significance extends beyond productivity metrics. The ability to build software is becoming accessible to a broader range of people – not because the underlying complexity has been eliminated, but because much of the mechanical translation between intent and implementation can now be delegated to an AI agent.

What this unlocks, in human terms, is closer to what the printing press unlocked for written communication: a dramatic expansion of who can participate.

Boris Cherny frames it precisely: "I imagine a world where everyone is able to program. Anyone can just build software anytime." He draws the parallel to the printing press explicitly – a technology that transferred a capability previously held by a small, specialized group to the general population, and that preceded an explosion of human creative and intellectual output.

This handbook exists to help you become a capable participant in that transition.

Chapter 2: Anthropic — Background and Purpose

Chapter 2 Header

Claude Code is a product of Anthropic. To understand the product fully, it's useful to understand the organization that built it.

Founding and Mission

Anthropic was founded in 2021 by Dario Amodei, Daniela Amodei, and several colleagues who had previously worked at OpenAI. The founding motivation was not competitive positioning. It was a principled disagreement about how AI development should proceed.

The founders believed – and continue to believe – that building powerful AI systems without a rigorous, primary commitment to safety constitutes one of the most consequential risks humanity has introduced. Their response was to establish an organization whose central purpose is to develop AI capability and AI safety in parallel, treating the latter not as a constraint on the former but as an equal and inseparable objective.

Three of Anthropic's co-founders co-authored the original scaling laws paper, one of the foundational documents of modern AI research. It describes mathematically how model capability scales with size and compute. These are people who understood the trajectory of AI capability before most of the industry had internalized it. Their choice to build an organization focused on safety reflects informed conviction, not just caution.

What Safety Means in Practice

At Anthropic, safety research manifests across multiple layers. The deepest is mechanistic interpretability: the scientific effort to understand what is actually happening inside a model at the level of individual computational components.

This is not an abstract exercise. As Boris Cherny describes it:

"We can identify a neuron related to deception. We are starting to get to the point where we can monitor it and understand that it's activating."

This work informs how models are trained, how they're evaluated, and how they're deployed. It also shapes Claude Code directly. Before public release, Claude Code ran internally at Anthropic for four to five months, with behavior studied carefully before any external release. This was not a formality. It reflected genuine uncertainty about how an agentic AI system would behave in conditions that training-time evaluations cannot fully anticipate.

Scale and Influence

By early 2026, Anthropic reached a valuation of over \(350 billion. Claude Code is reported to generate over \)2 billion in annual revenue and continues to accelerate: daily active users doubled in the month prior to this writing.

The company's models, particularly Claude Sonnet 4.6 and Claude Opus 4.6, are the current standard for serious AI-assisted software development across organizations from early-stage startups to the largest technology companies in the world.

Chapter 3: The Claude Model Family

Chapter 3 Header

Claude Code is powered by Anthropic's Claude models. The models are the intelligence underlying the system. Claude Code provides the environment – the tools, the interface, the scaffolding – but the models determine the quality of reasoning, planning, and execution.

Claude Sonnet 4.6

Claude Sonnet 4.6 is Anthropic's mid-tier model. It delivers strong performance across coding tasks – planning, implementation, debugging, documentation – at a meaningfully lower cost per token than Opus.

Sonnet 4.6 represents the inflection point at which Claude Code became broadly useful. Prior to this generation, models were capable but insufficiently reliable for production workflows. Sonnet 4.6 changed that, providing the reasoning depth required for real engineering work at a price accessible to individual developers and small teams.

For most development tasks of moderate complexity, Sonnet 4.6 is adequate. It handles single-feature implementations, debugging sessions, and documentation generation well. Where it reaches its limits (like in extended autonomous sessions, deeply architectural decisions, complex multi-step reasoning), Opus 4.6 becomes the appropriate choice.

Claude Opus 4.6

Claude Opus 4.6 is Anthropic's most capable model. Research measuring its performance on real software engineering tasks found that it achieves a time horizon of approximately 14.5 hours at 50% task completion rate – meaning it can handle unattended work that would occupy a skilled engineer for most of a working day.

Boris Cherny uses Opus 4.6 exclusively, with maximum effort enabled, and never reduces capability to save tokens. His reasoning is precise:

"Because a less capable model is less intelligent, it requires more tokens to do the same task. It is not obvious that using a cheaper model is actually cheaper. Often, the most capable model is cheaper and less token-intensive because it completes the task faster with less correction."

Opus 4.6 is Anthropic's first ASL-3 class model – a designation in their safety classification framework applied to models of sufficient power that the most rigorous safety protocols are warranted before and after release.

Claude Haiku

Claude Haiku is Anthropic's lightest model that's fast, inexpensive, and suited to simple tasks: summarization, brief lookups, lightweight generation. For Claude Code work, Haiku is rarely the right choice. It lacks the reasoning depth required for meaningful software development.

Model Selection Guide

Task Characteristics Recommended Model
Initial exploration, learning Claude Code Sonnet 4.6
Moderate complexity development work Sonnet 4.6
Complex architectural decisions Opus 4.6
Extended autonomous sessions Opus 4.6
Multi-agent parallel workflows Opus 4.6
Simple lookups, trivial queries Haiku

The practical guidance is straightforward: if you can, don't select a model primarily based on cost. A less capable model that requires more correction cycles, more context clarification, and more tokens to reach an acceptable output frequently costs more in total than a more capable model that completes the task in fewer passes.

Chapter 4: What Claude Code Is

Chapter 4 Header

Claude Code is an AI agent for software development. That definition requires unpacking.

The Distinction from Conversational AI

A conversational AI system – ChatGPT, Claude.ai in basic form, most early AI products – produces text in response to text. It can explain, summarize, translate, draft. It generates output that a human then acts upon. The AI does not itself act in the world.

Claude Code is categorically different. It is an agent: an AI system equipped with tools that allow it to act. In a Claude Code session, the model doesn't merely generate a code snippet and return it to you. It reads the files in your project, writes and modifies those files, executes terminal commands, installs packages, runs tests, searches the web, commits to version control, opens pull requests...the list goes on.

The distinction matters. When you engage Claude Code on a development task, you aren't prompting a text generator. You're directing an autonomous agent that can execute sequences of actions, make decisions about which tools to employ, and produce material results in your codebase.

The Agent Architecture

Most AI-powered development tools are built by constraining the model: defining rigid workflows, controlling what the model can see, specifying precisely which tools it can use in which sequence. This creates predictability at the cost of flexibility and capability.

Claude Code's architecture inverts this. As Boris Cherny describes it: "The product is the model." The approach is to expose the model as directly as possible, with a minimal set of tools and minimal scaffolding, and allow the model to determine the best approach for a given task. The model decides which tools to use, in what order, and how to combine them.

This approach trusts the model's judgment. With Claude Opus 4.6, that trust is warranted. The model can assess a complex problem, formulate a strategy, execute it using available tools, and adapt when it encounters unexpected conditions — without constant human intervention.

Where Claude Code Runs

Claude Code is available across multiple surfaces:

  • Terminal (Mac, Windows, Linux)

  • VS Code extension

  • Claude desktop application (Code tab)

  • Claude iOS and Android applications

  • GitHub integration (automated code review and PR management)

  • Slack integration

The underlying agent is identical across all surfaces. The interface differs, but the capability does not.

This breadth is a deliberate expression of product philosophy: bring the tool to wherever people already work, rather than requiring people to adapt to a new environment. Boris describes this as "latent demand" – a principle that shaped both Claude Code's original terminal deployment and its subsequent expansion.

Current Impact

  • 4% of all global GitHub commits are authored by Claude Code (early 2026)

  • Projected to reach 20% of all commits by end of 2026

  • Anthropic's daily active users doubled in the preceding month

  • Engineering productivity at Anthropic has increased 200% since Claude Code adoption

  • 100% of pull requests at Anthropic are reviewed by Claude before human review

Boris describes the growth trajectory as still accelerating: "It's not just going up – it's going up faster and faster."

Chapter 5: Why the Development Community Required This

Chapter 5 Header

Claude Code did not emerge from a product strategy. It emerged from a genuine need in how software is built, and from an observation about where friction in that process actually lives.

The Mechanical Burden

Professional software development involves significant mechanical work that has nothing to do with the intellectual challenge of building good systems. A large portion of a developer's day, in a typical codebase, involves:

  • Looking up documentation for APIs whose syntax changes between versions

  • Writing authentication and authorization boilerplate for the hundredth time

  • Setting up database connections, environment configurations, REST endpoints

  • Decoding opaque error messages

  • Searching for solutions to problems that are, with near certainty, already solved somewhere

  • Writing tests for behavior whose correctness is already known

  • Managing dependency conflicts and breaking changes

None of this is where engineering expertise is exercised. It is the overhead of translation: converting understanding into syntax, correct syntax, in the right files. Boris describes it as "the minutia" and "the tedious parts" – things that consumed time without demanding the faculties that matter.

Claude Code eliminates this overhead. The mechanical translation is handled by the agent. What remains for the engineer is the part that requires judgment: what to build, how to architect it, whether it is correct, whether it serves its purpose.

The Access Barrier

Beyond the tedium experienced by professional developers, there is the structural barrier facing everyone else. Building functional software has required years of technical training. The ideas exist broadly. The capacity to execute them has been concentrated in a small technical population.

Claude Code redistributes that capacity. It doesn't eliminate the need for understanding and judgment (a point this handbook will return to repeatedly), but it dramatically lowers the threshold at which someone can produce working software. A product manager, a scientist, a business owner, a domain expert with a clear idea can now begin building in a way that was not previously available to them.

The Feedback Loop Problem

Senior engineers frequently know exactly what they want but find it difficult to convey that precisely enough to produce it consistently. The distance between a specification and its implementation – what engineers call the specification gap – is one of the chronic sources of friction in engineering teams.

With Claude Code, that gap narrows substantially. The developer remains in the loop throughout, reviewing plans before they are executed, inspecting output as it is produced, redirecting when necessary. The feedback cycle collapses from days to minutes. Misalignments are caught early, when they are cheap to fix.

Chapter 6: Installation and Initial Setup

Chapter 6 Header

This chapter covers everything required to have Claude Code running on your machine from a position of zero prior configuration.

Step 1: Set Up a Claude Account

Before installing Claude Code, you will need an account on Claude.ai.

  1. Navigate to claude.ai in a browser

  2. Select "Sign Up" and create an account using your email address

  3. Confirm your email address

  4. Your account is now active

This account authenticates you across all Anthropic products, including Claude Code.

Step 2: Select a Subscription Plan

Claude Code requires a paid subscription. The available tiers as of 2026:

Claude Pro: $20 per month

The Pro plan provides access to Claude's models with a daily usage limit. When you reach that limit, you wait until the following day to continue.

For someone beginning with Claude Code building small projects, learning the system, running sessions of moderate intensity, the Pro plan is sufficient. It isn't designed for sustained professional use, but it's an appropriate entry point.

Claude Max: \(100 or \)200 per month

The Max plan substantially increases or effectively removes usage limits. At the $200 tier, Anthropic's own team describes never encountering the usage ceiling under normal working conditions.

If Claude Code becomes a primary instrument in your workflow – which it will, if you use it consistently – the Pro plan will constrain you. Upgrade to Max when you begin hitting limits regularly.

On the cost question: Claude Code at the \(20 tier represents a low threshold for access to something that materially changes what one person can build. The question is not whether the tool is worth the cost. The question is whether you will use it consistently enough to benefit from it. Begin at \)20. The answer will be evident within a few weeks.

Step 3: Access the Installation Documentation

Navigate to code.claude.ai. This is Anthropic's official installation hub for Claude Code. It provides:

  • Installation commands for Mac and Linux (via npm)

  • Installation instructions for Windows (via PowerShell)

  • Links to IDE extensions

If you are comfortable in a terminal, the single-line npm installation command is sufficient. If not, proceed to the VS Code method described in the following chapter.

Chapter 7: VS Code and the Claude Code Extension

Chapter 7 Header

For those without a prior terminal workflow, Visual Studio Code provides the most accessible entry point into Claude Code. This chapter covers that setup completely.

Why VS Code

Visual Studio Code is a free, open-source code editor distributed by Microsoft. It holds over 70% market share among developers and is the environment in which the majority of professional software development occurs today – not because it's technically superior to all alternatives, but because it is well-designed, extensible, and broadly supported.

The Claude Code extension for VS Code provides a graphical interface to the same underlying agent you would access through a terminal. Within this interface:

  • Your project files are visible in a persistent sidebar

  • Claude's file edits appear in a diff view (additions in green, removals in red) before they are applied

  • You can review, approve, or reject individual changes

  • You can open any file Claude references directly from the Claude panel

  • The full terminal, if you need it, remains accessible

Diff View of Proposed Edit

This environment is appropriate for any skill level. Experienced developers benefit from the tight integration. Those new to development benefit from the visibility, as it's always clear what Claude is doing and why.

Installing VS Code

  1. Navigate to code.visualstudio.com

  2. Download the installer for your operating system

  3. Run the installer and accept the default configuration at each step

  4. Launch VS Code at completion

Installing the Claude Code Extension

  1. In VS Code, locate the Extensions panel in the left sidebar. The icon resembles four squares, three arranged in an L with one offset

  2. In the search field, enter Claude Code

  3. Select the official Anthropic extension and confirm it shows several million downloads

  4. Click Install

VS Code Extensions Search

When installation completes, a Claude Code icon will appear in the VS Code toolbar. This opens the Claude Code panel.

Claude Code Icon in Activity Bar

Opening a Project

Claude Code operates within a directory – a folder containing your project files. Before beginning, create a folder for your project and open it in VS Code:

  1. Create a new folder anywhere on your system

  2. In VS Code: File → Open Folder

  3. Select and open the folder

VS Code will display the (currently empty) folder in its sidebar. Claude Code will read from and write to this directory throughout your session.

Click the Claude Code icon. The Claude Code panel opens. You're ready to begin.

Empty Claude Code Side Panel

Initial Configuration

The first time you open Claude Code, type /model to select which model you want to use. For initial sessions, Sonnet 4.6 provides a reasonable starting point.

Next, review permissions by typing /permissions. The default setting requires Claude to ask before modifying any file. This is appropriate until you are comfortable with how Claude operates.

You can type /help to see all available commands.

Chapter 8: Subscriptions, Token Costs, and Usage

Chapter 8 Header

Having a working understanding of token economics will help you make better decisions about how you use Claude Code.

What Tokens Are

Every interaction with a Claude model consumes tokens. A token corresponds roughly to three-quarters of a word – so a prompt of 100 words and a response of 300 words represents approximately 530 tokens total.

Token consumption accumulates across a session. Each message you send, each response Claude generates, each file Claude reads – all of this is tokenized and counted. On subscription plans, this accumulated usage is measured against your plan's daily allowance.

Where Token Consumption Accumulates

Brief queries consume trivially few tokens. But the kind of work Claude Code is built for (reading through an existing codebase, planning a feature, implementing it, correcting course, running verification) can consume tens of thousands of tokens in a single session.

Advanced users running multiple parallel agents consume far more. Boris Cherny notes that some engineers at Anthropic spend hundreds of thousands of dollars monthly in token costs. For those devs, Claude Code has replaced what would otherwise require entire engineering teams.

For someone beginning, usage will be modest. Token costs at the Pro tier are not a practical concern during the learning phase.

The Cost of Capability Reduction

As I mentioned above, Boris Cherny's advice on model selection addresses a counterintuitive point: using a less capable model to reduce token costs often increases total token consumption. A model with weaker reasoning requires more correction passes, generates more context clarification, and takes longer to converge on an acceptable result. The less capable model costs less per token and often costs more per task.

His recommendation: use the most capable model available. Currently, that is Opus 4.6. Reduce model capability only when performance requirements are demonstrably lower and the trade-off is understood.

The broader principle he articulates: "Don't try to optimize too early. Give engineers as many tokens as they need. At the point where something is proven and scaling, then optimize. Not before."

Practical Guidance

Profile Plan
Learning, initial projects Pro ($20)
Regular personal development Max ($100)
Professional, sustained use Max ($200)
Team or API-level usage Anthropic API (usage-based)

The rule is this: begin at the plan that doesn't actively frustrate your usage. If you reach limits and want to continue, that's information. It means you have integrated the tool into your work. Upgrade at that signal.

Chapter 9: Working in Your First Session

Chapter 9 Header

With Claude Code installed and a project directory open, the first session is an exercise in learning how the system communicates and responds.

Beginning Simply

The appropriate starting point is a project with immediately visible output. If you're new to software development, web projects are optimal: you write files, open a browser, and see the result directly. The feedback loop is fast and unambiguous.

A minimal first task might look like this:

Code Snippet

Build a personal homepage. Include a name, a short biographical description,
and links to LinkedIn and Twitter. The design should be clean and dark-themed.
Use HTML and CSS only — no JavaScript frameworks.

Claude Code with First Prompt

Claude Code will:

  1. Assess the request and formulate a plan

  2. Request permission to create the specified files (if operating in default mode)

  3. Write the HTML and CSS files

  4. Confirm what was produced

Claude Code Plan Mode Output

Open the resulting index.html file in a browser. Assess whether it meets your intent. Where it does not, state precisely what needs to change. This cycle – prompt, output, assessment, refinement – is the fundamental working method.

Generated index.html File visible

Learning Through Iteration

Proficiency with Claude Code develops through use, not through reading. Each each cycle of prompt, output, and refinement builds judgment about how to specify intent clearly, how to evaluate Claude's output, and how to correct course efficiently.

This is not unique to AI tools. It's how expertise in any instrument develops. Begin with modest scope, observe closely, and adjust. The speed at which fluency develops is proportional to how actively you engage with each result rather than accepting or rejecting it without analysis.

Chapter 10: Prompt Discipline — Inputs Determine Outputs

Chapter 10 Header

The relationship between prompt quality and output quality is direct and consistent. This is not a limitation of Claude Code. Rather, it's a property of any system that must translate intent into action. The more precisely intent is expressed, the more accurately it can be executed.

The Core Principle

Poor results from Claude Code are almost never attributable to model incapability. They are attributable to underspecified prompts. When Claude produces something that doesn't match what you wanted, the correct diagnostic question is: was my specification sufficient to produce what I wanted?

In most cases, it was not.

Claude operates on what it receives. If you provide an abstract description of a product, Claude fills the gaps with its own reasonable assumptions. Where those assumptions deviate from your expectations – and they will – the output disappoints. The gap is not between what Claude can do and what you need. It is between what Claude knew and what it needed to know.

What Specification Requires

Effective prompts are specific, contextual, and feature-oriented. Consider the difference:

Underspecified:

Code Snippet

Build a task management app.

Adequately specified:

Code Snippet

Build a task management application for a three-person team. Requirements:

1. Tasks have a title, optional description, due date, and priority level (low, medium, high)
2. Each task can be assigned to one of three hardcoded users: Alice, Bob, or Carol
3. Tasks can be marked complete, with the completion timestamp recorded
4. The task list can be filtered by assignee or by priority
5. All data persists in localStorage — no backend required
6. Interface: clean, light theme; no external CSS frameworks

Technology: HTML, CSS, vanilla JavaScript only.

The second version closes all the significant decision points Claude would otherwise resolve by assumption. It produces a result substantially closer to intent on the first pass.

Underspecified vs Well-Specified Prompt

Explicit Over Implicit

State what you want Claude to do even when it seems obvious. If you want Claude to examine documentation before writing code, say so. If you want specific library versions, specify them. If you want no changes to existing files, make that explicit. If you want a particular file structure, describe it.

Implicit expectations are expectations that are frequently not met. Explicit instructions are instructions that are consistently followed.

The Specificity Standard

A practical test: if a competent engineer read your prompt with no other context, could they build exactly what you have in mind? If not, the prompt is not yet specific enough.

This standard is useful because it surfaces the actual gaps: the decisions you haven't yet made, the constraints you haven't yet articulated, the behavior you haven't yet defined. Filling those gaps before Claude begins building is far more efficient than discovering them in the output.

Chapter 11: Planning as a Core Practice

Chapter 11 Header

Planning is the highest-leverage activity in Claude Code development. It's also the most commonly underinvested.

Why Planning Determines Outcomes

A well-specified plan, reviewed and approved before any code is written, produces three effects:

  1. Claude invests its reasoning in the right problem

  2. Misalignments between intent and approach are caught before they are embedded in code

  3. The resulting implementation is coherent, because it follows a coherent design

Without adequate planning, Claude makes architectural and implementation decisions autonomously, based on what it infers from limited input. Some of those decisions will be correct. Some will not. Discovering which ones were wrong after the codebase has been built around them is expensive. It requires reading, understanding, and correcting code that was generated at significant token cost.

The ratio of planning time to development time that produces optimal outcomes is higher than most people initially expect. Thirty minutes of structured planning frequently reduces a ten-hour build to three hours. The mathematics are not subtle.

Plan Mode

Claude Code includes a dedicated Plan Mode. In this mode, Claude reasons through the task and produces a structured plan – which files will be affected, what the implementation sequence will be, how data will flow, what edge cases need to be handled – without writing a single line of code.

You review the plan. You can question it, modify it, reject portions of it, or add constraints. Only when the plan reflects your actual intent do you release Claude to begin implementation.

Boris Cherny uses Plan Mode for approximately 80% of his sessions. The mechanism itself is disarmingly simple: a single sentence injected into the model's context: "Please do not write any code yet." That single instruction changes Claude's behavior from execution to structured reasoning.

To activate Plan Mode:

  • In the terminal: press Shift+Tab twice

  • In VS Code or the desktop app: click the Plan Mode button in the interface

Plan Mode Button in VS Code

The discipline here is important: actually read the plan Claude produces. Don't approve it reflexively. The plan is the point at which you can intervene at minimum cost.

Example Plan Mode Detail

The Product Requirements Document

The formal output of a planning session is a Product Requirements Document (PRD). For individual projects, this need not be elaborate. It should contain:

  1. A clear statement of what is being built and for whom

  2. A specific list of features, each described in behavioral terms

  3. Technical requirements: technology stack, database, external APIs, browsers to support

  4. Interface parameters: visual style, layout decisions, interaction model

  5. Explicit criteria for what constitutes a feature being complete

A PRD.md file at the project root, readable by Claude Code at the start of each session, provides consistent context that persists across sessions. The quality of this document directly determines the quality of every subsequent build session.

The Interview Approach

A useful technique for generating a complete PRD is to instruct Claude to interview you about the project before planning anything:

Code Snippet

I want to build [project description]. Before writing any plan or code,
please use the Ask User Question tool to interview me systematically —
covering technical requirements, feature specifications, UI decisions,
data model, and any trade-offs I should consider.
Do not proceed to planning until the interview is complete.

This surfaces decisions you may not have consciously formulated. Some questions will have obvious answers. Others will reveal gaps in your own thinking – gaps you would prefer to close before they manifest as incorrect implementation decisions.

Chapter 12: Building Feature by Feature

Chapter 12 Header

A planned project is built incrementally. You should resist the instinct to attempt complete implementation in a single pass. Feature-by-feature development isn't slower – it's more reliable, more verifiable, and ultimately faster.

The Case for Incremental Development

Each feature implemented is a unit of behavior that can be verified independently. If Feature 2 is built on top of Feature 1 without verifying Feature 1, defects compound. A flaw in the foundation propagates upward, embedding itself in everything built above it. Discovering that flaw late multiplies the cost of correcting it.

Each verified feature is also a stable platform from which the next feature can be built with confidence. The accumulation of verified, working behavior is what a production system is.

There is also the matter of understanding. A developer who has watched – and reviewed – Claude build each feature, one at a time, understands how the system works. That understanding is necessary for directing effective corrections, making informed architectural decisions, and explaining the system to others.

The Build Cycle

For each feature in the PRD:

  1. Specify the feature in detail. Provide the feature description and any supplemental context: files that will be involved, constraints on implementation, acceptance criteria.

  2. Enter Plan Mode and review the plan. Read Claude's proposed approach. Is it consistent with your design intent? Will it affect files it shouldn't touch? Is the data flow correct? Revise the plan if necessary.

  3. Approve and release to implementation. Once the plan is sound, release Claude to implement. Review the diff view for each file change.

  4. Test the feature. Manually exercise the feature's intended behavior. Deliberately test boundary conditions. Does it behave correctly when data is missing? When values are at their limits? When the user does something unexpected?

  5. Address any discrepancies. State precisely what is incorrect and ask Claude to correct it. Specificity in correction is as important as specificity in the original specification.

  6. Confirm and advance. When the feature works correctly, move to the next one.

Diff View of Single Feature Change

Appropriate Starting Projects

If you're new to software development, the best initial projects are small, web-based, and produce immediately visible output. Examples:

Project Rationale
Personal homepage Immediate visual feedback, single HTML/CSS file
Simple calculator Concrete logic, verifiable output
Task list application Multi-feature structure, foundational CRUD pattern
Static portfolio Practical result, shareable immediately
Data display dashboard Introduction to structured data and layout

Web projects require no server configuration, no deployment setup, and no build pipeline. Open a browser, load a file, observe the result. The feedback cycle is as short as possible, which makes the learning cycle as short as possible.

Chapter 13: How Claude Code Actually Works

Chapter 13 Header

Understanding the mechanics of Claude Code at a technical level changes how you use it. When you know what's happening inside a session, you write better prompts, diagnose problems faster, and make better decisions about when to intervene and when to let the agent proceed.

The Agent Loop

At its core, Claude Code operates as a reasoning loop. Every session, at every step, follows the same underlying cycle:

  1. Receive input: a message from you, or the result of a previous tool call

  2. Reason: determine what action to take next

  3. Select a tool: choose which capability to invoke

  4. Execute the tool: take the action

  5. Observe the result: receive what the tool returned

  6. Reason again: determine the next action given the new information

  7. Repeat: until the task is complete or input is required

This cycle is not visible in the interface. You see messages and file edits. Internally, Claude is running through this loop many times per task: reading a file, thinking about what it implies, reading another file, forming a plan, writing a change, running a command to verify it, reading the output, and deciding whether to adjust.

The model is the reasoning engine. The tools provide the hands. The loop provides the structure.

How Tool Calls Work

Claude Code has access to a specific set of tools. From the model's perspective, these are callable functions, each with a name, a set of parameters, and a return value.

When Claude decides to read a file, it doesn't "see" the file. It generates a structured tool call: read_file(path="src/auth.js"). The tool executes and returns the file's contents. Claude receives that content and continues reasoning.

The tools available to Claude Code include, in simplified form:

Tool What It Does
read_file Returns the contents of a file as text
write_file Writes content to a file, creating it if it does not exist
edit_file Applies a specific change to an existing file
run_command Executes a shell command and returns stdout and stderr
list_directory Returns the file and directory structure of a path
search_files Searches file contents for a pattern
web_search Queries the web and returns results
ask_user Pauses and requests input from you
browser Opens a browser and interacts with a web page

When Claude Code runs a test suite, it issues run_command("npm test"), receives the output, reads which tests failed, then uses edit_file to apply corrections. Then it runs the command again. When it is exploring an unfamiliar codebase, it issues list_directory to understand the structure before opening specific files.

This is why Plan Mode is so powerful: it lets Claude perform the reasoning portion of the loop – deciding which tools it would use, in what sequence, for what purpose – without actually executing those tools. You see the proposed sequence before anything happens.

How Claude Reads a Codebase

When you open a project in Claude Code, Claude does not automatically read all your files. It reads selectively, on demand, as the loop requires.

A typical codebase exploration sequence:

  1. Claude issues list_directory on the root and sees the top-level folder structure

  2. It identifies meaningful directories (src/, app/, components/, etc.) and reads those

  3. For the specific task at hand, it reads the files most likely to be relevant: the component being modified, the service being called, the configuration being adjusted

  4. If it encounters an import or reference to something it has not read yet, it reads that file too

This selective, on-demand reading is efficient but has implications you should understand:

First, Claude's view of your codebase is always partial. At any given point in a session, Claude has read some of your files and not others. If something relevant exists in a file Claude has not read, Claude doesn't know about it.

This is why being explicit in your prompts matters: if a constraint or convention lives in a file Claude has not been asked to read, it will not apply that constraint unless you tell it to or point it to the file.

Second, the CLAUDE.md file is read first, always. Because of this, your CLAUDE.md is the most reliable place to encode conventions. It is the one piece of context Claude has before it reads anything else.

And finally, you can direct Claude's attention. In your prompts, you can name specific files: "Before implementing this feature, read src/auth/middleware.js and src/db/schema.js." Claude will read those files first, incorporate their content into its understanding, and apply that understanding to the task.

How Claude Assembles Context for Each Step

Each step of the loop constructs what is called a context – the full set of information the model receives before generating its next response. This context includes:

  • The conversation history so far (your messages, Claude's responses)

  • The results of all tool calls in this session

  • The contents of any files Claude has read

  • The CLAUDE.md file (if present)

  • Any system-level instructions from Anthropic

This entire assembled context is what the model "sees." There is no memory outside it. Nothing persists from one session to the next except what exists in files on disk.

This is why context management matters. A session with a long conversation history, multiple large file reads, and extensive tool output will have a full context by the time it reaches 40–50% of the window limit. The model reasons over everything in context simultaneously — when that window is too full, the model begins to weight recent inputs more heavily than earlier ones, and coherence with decisions made early in the session can degrade.

Why the Model's Judgment Matters

Because Claude Code's architecture exposes the model directly – without tight scripted workflows constraining what it can decide – the model must exercise genuine judgment at each step of the loop. It decides:

  • Which files are worth reading given the task

  • Whether a plan needs adjustment based on what it discovered in a file

  • Whether a test failure is a real bug or a test that needs updating

  • Whether to ask you a question or make a reasonable assumption and proceed

  • When a task is genuinely complete vs. when further verification is warranted

This is the practical meaning of "the product is the model." Claude Code's quality is the model's quality. The tools are consistent. The loop is consistent. What varies from one model generation to the next is the quality of reasoning at each decision point. And that is everything.

Chapter 14: Architecting Applications Well with Claude Code

Chapter 14 Header

Writing a prompt that produces a feature is one skill. Designing an application that Claude Code can build reliably, maintain coherently, and extend over time is another, deeper skill.

This chapter covers the structural principles that make Claude Code development efficient and the codebases it produces maintainable.

The Connection Between Structure and Prompt Quality

There is a direct relationship between how a codebase is organized and how well Claude Code can work within it. A well-structured codebase is one in which:

  • Each file has a single, identifiable responsibility

  • File and directory names accurately reflect their contents

  • Dependencies between components flow in one direction

  • Configuration is separated from logic

  • External integrations are isolated in defined boundaries

When a codebase has this kind of structure, Claude can read a small number of files to understand a given component, make targeted changes, and produce output that fits coherently with what already exists.

When a codebase is disorganized – logic mixed with presentation, files responsible for multiple unrelated things, dependencies tangled across layers – every change Claude makes requires reading more context to understand the system, and the probability of an unintended side effect increases. The prompts required become longer and more prescriptive. The review burden increases.

Good structure is not organization for its own sake. It is a productivity investment. A codebase that Claude Code can navigate efficiently is one that produces higher-quality output in fewer tokens.

The Single Responsibility Principle, Applied

The most important structural principle for Claude Code projects is also one of the most important in software engineering generally: each module should do one thing.

In practice, for a web application:

Code Snippet

src/
  components/      — UI components: each component renders one thing
  services/        — Business logic: each service owns one domain
  api/             — HTTP handlers: each handler manages one route group
  db/              — Data access: each module owns one entity
  utils/           — Pure utility functions: no side effects, no state
  config/          — All configuration: no configuration scattered elsewhere

When you ask Claude to "add email validation to the signup form," Claude reads components/SignupForm.jsx (the component), services/auth.js (the auth logic), and possibly utils/validation.js (shared validators). Three files. Focused change. Clean result.

If signup logic were embedded in a single large app.js file with all other logic, Claude would need to read everything, reason about what to touch and what not to, and work in a context where a single misplaced edit can affect unrelated behavior. The change is the same – the cost of making it is not.

Layer Separation as a Claude Code Multiplier

The pattern that consistently produces the best outcomes with Claude Code is a strict separation between layers of an application:

  1. Presentation layer: what the user sees and interacts with. Components, pages, templates. No business logic. No data fetching beyond what the component directly needs.

  2. Business logic layer: what the application does. Services, use cases. No knowledge of the database interface. No UI-specific concerns.

  3. Data access layer: how data is stored and retrieved. Repository pattern or service layer specific to database interaction. No business logic. No presentation concerns.

  4. Integration layer: connections to external services (APIs, email providers, payment processors). Strictly isolated so that the rest of the system does not depend on the implementation details of external services.

When these layers are enforced, both in the file structure and in the CLAUDE.md conventions, Claude Code respects them automatically. It knows that a feature touching the UI does not require changes to the data access layer. It knows that an email integration lives in one place and is called through a defined interface. Changes are targeted. Side effects are minimal. Reviews are coherent.

How to Structure a New Project for Claude Code

When beginning a new project, spend time on the directory structure before asking Claude to write any code. Establish the structure, write the CLAUDE.md, and then begin feature implementation.

A practical sequence:

Code Snippet

1. Create the directory structure manually (or ask Claude to create it from a spec)
2. Write CLAUDE.md with: stack, conventions, layer rules, library choices
3. Create a PRD.md with the full feature list
4. Ask Claude to implement a skeleton — empty files in the right places,
   with the right imports and class/function signatures, but no real logic yet
5. Review the skeleton — is the structure right? — before building on it
6. Implement features one at a time into this established structure

Step 4 is particularly valuable: a skeleton gives you a complete view of the application's shape before any logic exists. Structural mistakes – like a component in the wrong layer, a misplaced dependency, a missing interface – are visible and cheap to fix. Once logic is written into the wrong structure, correcting the structure requires rewriting the logic too.

What Claude Code's File Edits Tell You About Your Design

Pay attention to which files Claude touches when implementing a feature. Specifically:

If Claude is modifying more than 3–5 files for a single feature, the feature may not be coherently scoped, or the application structure may have too many dependencies between components.

If Claude is modifying the same files repeatedly across different features, those files are taking on too much responsibility.

If Claude asks clarifying questions before beginning, the specification was not complete enough or the existing structure is ambiguous about where the feature should live.

Each of these is a signal. Read it as information about the design, not as criticism of the instruction. Well-designed systems produce focused changes. Tangled systems produce sprawling changes.

Naming and the Navigation Problem

Claude Code navigates your codebase by reading file and directory names, examining import statements, and searching for patterns. Names are therefore not cosmetic. They are structural.

A file named utils.js tells Claude almost nothing about what is inside it. A file named validation.js, dateFormatters.js, or currencyUtils.js tells Claude exactly where to look when it needs that functionality.

Enforce these naming standards in your CLAUDE.md:

Code Snippet

## Naming Conventions

- Components: PascalCase, descriptive noun (UserProfileCard, TaskListItem)
- Services: camelCase, domain noun (authService, notificationService)
- Utilities: camelCase, descriptive of the concern (dateFormatters, currencyUtils)
- API handlers: camelCase, resource-oriented (userHandlers, taskHandlers)
- Test files: same name as the file being tested, with `.test.js` suffix

With these rules in CLAUDE.md, Claude will follow them consistently. Your code will be navigable, both by Claude in a session, and by developers (including yourself) returning to the project later.

Defining Done in Your CLAUDE.md

One of the highest-value additions to any CLAUDE.md is a clear definition of what "done" means for a feature. Developers use different standards. Claude Code should use yours:

Code Snippet

## Definition of Done

A feature is complete when:

1. The specified behavior works correctly across all described scenarios
2. Edge cases identified in the specification are handled
3. All new functions have JSDoc comments
4. A corresponding unit test exists and passes
5. No linting errors are introduced
6. The feature works correctly at desktop and mobile viewport widths

With this definition present, Claude will not declare a feature finished when the happy path works and the edge cases have not been tested. It will complete all steps in the definition before telling you the task is done.

Chapter 15: Plan Mode, Edit Mode, and Operational Modes

Chapter 15 Header

Claude Code's operational modes govern how it behaves when executing a task. Understanding these modes prevents common errors and allows you to calibrate the level of oversight appropriate to your context.

Plan Mode

We talked about this in detail in Chapter 11. Claude reasons – it doesn't write. Use it to begin any task of non-trivial complexity. The cost of a few minutes in Plan Mode is invariably lower than the cost of correcting a misaligned implementation.

Plan Mode Button Location

Ask Before Edits

This is the default mode. Before modifying any existing file or creating new ones, Claude presents the proposed change and requests explicit approval.

Permissions Mode Selector Toggle

The presentation takes the form of a diff view: proposed additions displayed in green, proposed deletions in red. You can approve or reject each change individually.

This mode is appropriate whenever:

  • You're working in an unfamiliar codebase

  • You're building something for the first time with Claude Code

  • The consequences of an incorrect edit are significant

The overhead of reviewing and approving each change is not waste. It is learning. You understand what is being built because you have reviewed every element of it.

Automatic Edit Mode

In this mode, Claude writes files without asking for approval between each change. This is appropriate only after you've used Plan Mode and you've reviewed and approved the plan. Once you've confirmed that Claude's approach is correct, there's no additional value in approving each individual file write – the strategy has already been established.

Boris describes the transition:

"Once the plan looks good, I just let the model execute. I auto-accept edits after that. With Opus 4.6, it oneshots it correctly almost every time."

Don't default to this mode. Earn it by developing confidence in your own plan review. Automatic edits without plan review is the condition in which errors most readily compound.

Shift+Tab Shortcut for Plan Mode

Chapter 16: Context Windows and Session Management

Chapter 16 Header

Every Claude session operates within a context window, which is the total volume of information the model can hold simultaneously. When that window fills, older information is compressed or lost. Understanding this constraint is necessary for managing long sessions and multi-session projects effectively.

The Context Window

For Claude Opus 4.6, the context limit is 200,000 tokens – equivalent to roughly 150,000 words, or approximately 200–300 pages of text.

In a long development session, this fills faster than it appears to. Reading a large codebase consumes tokens. A lengthy conversation accumulates them. Plans, implementations, test outputs, corrections – all tokenized, all counting toward the window.

The symptom of context saturation is drift: Claude begins making decisions inconsistent with constraints it established early in the session. It forgets architectural decisions. It reverts to default assumptions. If you notice this, the session has likely consumed most of its available context.

The 50% Practice

A working convention among experienced Claude Code users: when context usage reaches 40–50%, begin a new session. This is conservative enough to avoid the degradation zone and preserves clarity throughout the active session.

Claude Code displays context usage as a percentage. Monitor it during long sessions.

Context Usage Percentage Indicator

Cross-Session Continuity

Beginning a new session does not mean beginning from zero. Every file you have written, every architectural decision encoded in your codebase, and every convention documented in plain text is still on disk. Claude can read all of it fresh at the start of a new session. What it cannot do is recall the conversation that produced it. That context lives only in the session that created it.

The solution is to make your project self-documenting. When a project is properly documented, a new session can reach productive context in under a minute, without reconstruction from memory.

The Four Continuity Documents

The most effective approach uses four files, each serving a distinct purpose:

CLAUDE.md — Project conventions and architecture context

This file lives at the project root. Claude Code reads it automatically at the start of every session, before reading anything else. It is the most reliable channel for encoding project-level context that must always be present.

A well-maintained CLAUDE.md contains:

  • Technology stack and why specific choices were made

  • Directory structure and what each major directory contains

  • Coding conventions (naming patterns, async style, error handling approach)

  • Library choices: what is used and what is explicitly prohibited

  • Layer rules (for example, "all database access goes through db/repos/ – no SQL in route files")

  • Security requirements specific to this project

  • Definition of done for a completed feature

PRD.md — What is being built

The Product Requirements Document defines the complete feature set in behavioral terms. It is the authoritative answer to "what should this system do." Claude reads the PRD at the start of any feature session to establish alignment between your intent and its implementation plan.

A PRD entry for a feature should include:

  • Feature title and one-line description

  • Behavioral specification (what happens when X, what happens when Y)

  • Acceptance criteria: what must be true for this feature to be considered complete

  • Edge cases that must be handled

README.md — Current implementation status

The README serves two purposes: it describes the project for anyone encountering it for the first time, and – for Claude Code purposes – it maintains a running record of what has been built and what remains. Update it as features are completed. A README with an accurate implementation status section allows a new session to pick up mid-project without needing a reconstruction conversation.

A practical status section looks like:

Code Snippet

## Implementation Status

### Completed

- [x] User authentication (JWT, register, login, logout)
- [x] Task creation and persistence (SQLite, full CRUD)
- [x] Task filtering by status and priority

### In Progress

- [ ] Email notification on task assignment (backend logic done; email provider integration pending)

### Remaining

- [ ] Team management (invite members, manage roles)
- [ ] Export to CSV

progress.md — Session-level notes and decisions

This is optional but valuable on longer projects. It serves as a journal: decisions made during development, approaches that were tried and rejected, open questions that require resolution, and notes on anything that will affect future implementation decisions.

Unlike the PRD (which is specification) and the README (which is completion status), progress.md captures the reasoning behind decisions — the things that would otherwise exist only in the chat history of a session that has since ended.

Where These Files Live

All four files live at the project root: the top-level directory that Claude Code is working within. This is where Claude looks first when it reads a project.

Code Snippet

my-project/
  CLAUDE.md        ← read automatically every session
  PRD.md           ← read at the start of feature sessions
  README.md        ← updated as features complete
  progress.md      ← optional; captures decisions and open questions
  src/
    ...

Session Files in VS Code Sidebar

How Claude Accesses Them

Claude Code reads CLAUDE.md automatically. For the other files, you provide an explicit instruction at the start of the session:

Code Snippet

New session. Read the following files in order:
1. CLAUDE.md
2. PRD.md
3. README.md (specifically the Implementation Status section)
4. progress.md

Confirm your understanding of the project state and tell me where we left off.

This instruction takes thirty seconds to write. Claude reads all four documents, synthesizes their content, and returns a summary of the project's current state. You correct any misunderstanding before proceeding. Then you begin the session from a position of shared, accurate context.

The discipline of maintaining these files – updating them as features complete, adding to progress.md when significant decisions are made – is the single most valuable habit you can build for multi-session projects. It costs minutes per session. It saves hours of reconstruction.

Chapter 17: MCP Servers and External Integrations

Chapter 17 Header

MCP (the Model Context Protocol) is Anthropic's open protocol for connecting AI agents to external tools, services, and systems. It was developed by the same team that built Claude Code and released as an open standard.

What MCP Enables

Out of the box, Claude Code can read and write files, execute terminal commands, and search the web. MCP extends this to include virtually any application or service that exposes a compatible interface.

When an MCP server is installed and connected, Claude Code gains the ability to act on that service directly – reading data from it, modifying data within it, triggering actions in it – without requiring any manual data transfer between Claude and the external system.

This is the difference between Claude handing you a LinkedIn post to publish manually and Claude publishing it directly, including scheduling and image attachment.

Representative MCP Applications

GitHub (included by default):

  • Opens pull requests directly from the Claude session

  • Reviews incoming PRs and adds inline comments

  • Monitors CI/CD failures and proposes corrections

  • No browser navigation required

Notion:

  • Reads project notes and specifications

  • Updates task status as work is completed

  • Creates documentation pages

Google Workspace:

  • Reads from and writes to Google Docs and Sheets

  • Composes and sends email via Gmail

Playwright (browser automation):

  • Opens a real browser session

  • Navigates to URLs and completes form interactions

  • Extracts structured data from web pages

Airtable / database integrations:

  • Reads from structured data sources

  • Writes results and updates records

Boris uses Claude to pay parking fines, cancel subscriptions, send Slack messages, maintain project tracking spreadsheets, and send reminders to team members – all via plain English instructions to an agent that executes these tasks through connected MCP servers.

Installing an MCP Server — Step by Step

Installing an MCP server connects Claude Code to an external service. Here is a complete walkthrough using the Filesystem MCP server, one of the most broadly useful and a good one to install first.

Step 1: Find the MCP server

Anthropic maintains an official registry of MCP servers at github.com/modelcontextprotocol/servers. Each server has an installation command and a configuration format. For third-party services, the service's own documentation will provide the MCP configuration.

Step 2: Install the server via Claude Code

Open a Claude Code session and type:

Code Snippet

Add the following MCP server at user scope so it is available across all my projects:

npx -y @modelcontextprotocol/server-filesystem /path/to/your/allowed/directory

The user scope flag means the server applies to all your projects — not just the current one. Use project scope if you want the server active only for the current directory.

Step 3: Provide configuration if required

Some MCP servers require API keys or configuration. For example, connecting to Notion:

Code Snippet

Add the Notion MCP server at user scope with the following configuration:
- Server package: @notionhq/notion-mcp-server
- API key: [your Notion integration token]

Claude Code writes the configuration to ~/.claude/mcp_settings.json automatically. You do not need to edit this file manually.

Step 4: Restart the session

After installation, type /restart or close and reopen your Claude Code session. The MCP server initializes on startup. You can verify connected servers by typing:

Code Snippet

/mcp

This lists all active MCP servers and their available tools. If the server appears in this list, it is connected and available for Claude to use.

MCP Command List Output

Step 5: Use it in a session

Once connected, you interact with the MCP-enabled service using plain English. Claude selects the appropriate MCP tool automatically:

Code Snippet

Create a new Notion page in the "Projects" database titled "Sprint 14 Planning"
and add sections for Goals, Tasks, and Blockers.

Claude issues the appropriate Notion API call through the MCP server, creates the page, and returns the result – without you opening a browser.

Practical MCP Integrations

GitHub: the most immediately valuable

GitHub MCP is included in Claude Code by default and is worth using from your first day. Common usage:

  • "Open a pull request for the feature branch feature/auth-redesign into main. Title: 'Auth: JWT token refresh'. Description: summarize the changes you made in this session."

  • "Review the open pull requests in this repository and tell me which ones have unresolved review comments."

  • "Check the CI pipeline status for the last commit on main."

  • "Create a GitHub Issue for the validation bug I just described and assign it to me."

GitHub MCP Action Executed

Playwright — for anything web-based

Playwright gives Claude a real browser. Useful for:

  • Extracting data from sites without public APIs

  • Testing your own deployed application by interacting with it as a user would

  • Completing multi-step web workflows (form submission, navigation, login)

A practical example: "Go to our staging environment at staging.myapp.com, log in with the test credentials in .env, create a new task, and verify it appears in the task list."

Slack

  • "Send a message to the #engineering channel: 'Auth service deployment is live. Monitor for errors over the next 30 minutes.'"

  • "Check the #bug-reports channel for any messages from the last 24 hours and summarize the issues reported."

Google Workspace

  • "Read the latest version of the Product Requirements in the 'Q2 Roadmap' Google Doc and create a Markdown version of it in PRD.md."

  • "Add a row to the 'Sprint Tracker' spreadsheet for today's date with columns: feature completed, time spent, issues encountered."

Filesystem Server

The filesystem MCP extends Claude's file access beyond the current project directory. Useful for:

  • Reading a reference implementation in another project directory

  • Accessing template files stored elsewhere on your machine

  • Writing output to a shared directory

MCP Security Considerations

MCP servers extend Claude Code's reach. This means they extend the potential impact of mistakes – or of malicious instructions – proportionally. A connected server can take real actions in real services. Treat that capability with corresponding care.

1. Grant only the access required

When configuring a filesystem MCP server, specify the directories it can access rather than granting root-level access. When configuring a Notion or Google Workspace server, use an integration token scoped to the specific pages or folders Claude needs – not a token that grants full account access.

2. Review before executing

When Claude proposes an action through an MCP server – particularly one that modifies data, sends messages, or affects production systems – read the proposed action before approving. A misunderstood instruction that creates twenty Notion pages or sends a Slack message to the wrong channel is recoverable. An action that deletes records from a production database may not be.

3. Be specific in your instructions

Vague instructions are more likely to produce unexpected actions through MCP. "Update the project tracker" is ambiguous. "Add a row to the Sprint Tracker sheet with today's date and the values X, Y, Z in columns B, C, D" is not. Precise instructions produce predictable actions.

4. Do not store credentials in prompts

If Claude asks for an API key or authentication credential in order to configure an MCP server, provide it once during setup. Do not include live credentials in recurring prompts. Store them in the MCP configuration file, which Claude Code writes to your local machine's config directory.

Starting MCP Integrations

Connect MCP servers that correspond to tools you already use. There is no value in connecting services you don't work with. The value of MCP is in reducing friction in existing workflows, not in creating new ones.

Begin with GitHub. It is pre-configured, immediately useful, and demonstrates the core value of MCP within the first session you use it.

Chapter 18: Agents, Sub-Agents, and Parallel Workflows

Chapter 18 Header

As your competency with Claude Code develops and improves, the natural extension is parallel operation: multiple Claude sessions running simultaneously, each handling a distinct workstream.

Parallel Session Structure

A software project typically involves multiple independent workstreams that don't require sequential execution. Frontend implementation, backend logic, test coverage, documentation – these can often proceed in parallel without introducing conflicts, provided each session works on different files.

Multiple Claude Code sessions, each assigned a specific scope, replicate the parallel capacity of a small team. One session builds the authentication system. Another builds the data visualization layer. A third writes tests for features already completed. Each operates independently; all write to the same disk.

Boris Cherny operates with five or more parallel sessions routinely. His description of the workflow: "I kick off one task, then something else, then something else, and go get a coffee while they run."

Running Parallel Sessions — A Concrete Example

Suppose you are building the notes application from Blueprint 4 in Chapter 25. You have completed the backend REST API and now want to build the frontend and write API tests simultaneously. Here is exactly how that works.

Window 1: Frontend session

Open VS Code. Open the Claude Code panel. Type:

Code Snippet

We are building the frontend for the notes application. The backend REST API is
already running on port 3001. Your scope for this session is the client/ directory only.

Read CLAUDE.md and PRD.md first. Then implement the three-panel frontend layout
described in the PRD:
- Sidebar with the note list (read from GET /api/notes)
- Editor panel for the active note (with auto-save on keystroke debounce)
- Empty state when no note is selected

Do not touch any files in the server/ directory.

Enter Plan Mode first. Show me your implementation plan before writing anything.

Window 2: Test suite session

Open a second VS Code window (or a second terminal). Open a new Claude Code session. Type:

Code Snippet

We are writing integration tests for the notes application REST API. Your scope
for this session is the server/tests/ directory (create it if it doesn't exist).

Read CLAUDE.md and the server/routes/notes.js file. Write integration tests that
cover:
1. GET /api/notes — returns array, returns empty array when no notes exist
2. POST /api/notes — creates note, returns it with an id, rejects missing title
3. PUT /api/notes/:id — updates note, returns 404 for nonexistent id
4. DELETE /api/notes/:id — deletes note, returns 204, returns 404 if not found

Use the jest + supertest stack. Create server/tests/notes.test.js.

Do not modify any files outside server/tests/.

Both sessions now run simultaneously. Session 1 builds the frontend. Session 2 writes the tests. Neither touches the other's files. You review both when they complete.

Parallel Claude Code Sessions in VS Code

The discipline that makes this safe:

Each session receives an explicit scope boundary ("your scope is the client/ directory only") and an explicit prohibition ("do not touch any files in the server/ directory"). These constraints prevent the primary failure mode of parallel sessions: two sessions modifying the same file in incompatible ways.

Sub-Agents

Claude Code can spawn sub-agents internally – additional Claude instances dedicated to specific components of a larger task. This happens automatically when Claude determines that parallel execution of sub-tasks is appropriate.

For example: given the instruction "audit the entire codebase for security vulnerabilities," Claude may spawn sub-agents for different modules, each producing a findings report, which Claude then aggregates into a single document. You submit one instruction and receive one coherent result.

This capability scales with model capability. Opus 4.6 operates autonomous sessions for 10 to 30 minutes reliably. Extended sessions of hours or more are reported in advanced deployments.

Git Worktrees for Safe Parallel Development

What a Git worktree is

Normally, a Git repository has one working directory: the folder where your files live and where you make changes. A Git worktree is an additional working directory linked to the same repository. Each worktree checks out a different branch, and each has its own set of files on disk.

The result is that you can have multiple branches of the same repository active simultaneously, each in its own folder. A Claude Code session pointed at one folder sees only that branch's files. It cannot accidentally modify another branch's files because those files are in a different directory.

This is the cleanest available mechanism for running parallel agent sessions on a shared codebase.

Setting up worktrees for parallel agents

Say your main branch is main and you want two agents working in parallel – one on the authentication feature and one on the notification system.

Step 1: create a worktree for each feature branch:

Code Snippet

# From the repository root on main
git worktree add ../my-project-auth feature/authentication
git worktree add ../my-project-notifications feature/notifications

This creates two new directories at the same level as your project root:

  • ../my-project-auth/ – a full copy of the repository, checked out to feature/authentication

  • ../my-project-notifications/ – a full copy, checked out to feature/notifications

Step 2: open each worktree in its own Claude Code session:

Open VS Code. Open ../my-project-auth as the project folder. Start a Claude Code session scoped to the authentication feature.

Open a second VS Code window. Open ../my-project-notifications. Start a Claude Code session scoped to notifications.

Both sessions run against the same repository but in isolated branches. File conflicts are impossible, as each session's changes live in a separate directory. When work is complete, you merge normally:

Code Snippet

cd my-project
git merge feature/authentication
git merge feature/notifications

When to use worktrees

Worktrees are appropriate when:

  • You are working on a production codebase where conflicts are costly

  • Multiple sessions will be touching overlapping parts of the directory structure

  • You need clean version history with each feature isolated on its own branch

They aren't necessary for simpler parallel sessions with clearly bounded scopes. A single working directory, divided among sessions by explicit file-scope instructions, is sufficient for most work. When a branch's work is complete and verified, it's merged into the main branch through the standard review process.

This requires familiarity with the Git workflow. It's not necessary at the beginning, but it becomes important as the scale and complexity of parallel workstreams increases.

Chapter 19: Skills, Rules, and Persistent Instructions

Chapter 19 Header

Claude Code can be given project-specific instructions that persist across sessions – applied consistently without requiring re-specification in each new conversation. These are called Skills (in Anthropic's terminology) or, more simply, rules.

What Persistent Instructions Accomplish

Every project has standards: how files are named, what libraries are used and which are prohibited, what the testing coverage requirements are, how authentication must be implemented, what the database access patterns are. These standards exist to ensure that the codebase remains coherent across contributions, sessions, and time.

Without persistent instructions, you re-specify these standards in each session. With them, Claude knows the project's conventions from the moment a session begins. The quality of output aligns with your standards without requiring constant specification.

The CLAUDE.md File

The primary mechanism for persistent instructions is a file named CLAUDE.md at the project root. Claude Code reads this file at the start of every session.

A well-written CLAUDE.md contains:

Code Snippet

# Project: [Name] — Claude Context

## Architecture

[Technology stack, infrastructure, auth mechanism, external services]

## Conventions

- [Naming conventions]
- [File organization]
- [Async patterns — e.g., always async/await]
- [Data access patterns — e.g., all DB calls through service layer]

## Libraries

[What is used, what is prohibited]

## Testing

[Framework, coverage requirements, testing patterns]

## Security

[Specific security requirements — credential handling, input sanitization specifics]

## Definition of Done

[What constitutes a completed feature before it can be marked complete]

With this file in place, every Claude session for this project begins with full knowledge of the codebase's standards. You don't need to repeat yourself. The codebase doesn't drift from its own conventions.

Ecosystem-Level Skills

Organizations and platforms are beginning to publish standardized Skills. These are pre-written instruction sets that encode best practices for building on their platforms. Vercel has launched this initiative for their hosting and deployment platform, enabling Claude Code to make correct deployment decisions without explicit guidance.

This represents a direction in which the ecosystem will develop further: a library of verified, platform-specific instruction sets that any developer can include in their project, encoding decades of accumulated engineering judgment into Claude's available context.

Chapter 20: Autonomous Loops — Conditions for Use

Chapter 20 Header

Autonomous loop operation – where Claude Code executes a task sequence without human approval between steps – is frequently discussed and frequently misapplied. This chapter describes the conditions under which it is appropriate and the conditions under which it is not.

The Structure of a Loop

In autonomous operation, Claude Code receives a task list and works through it sequentially without pausing for approval. It makes decisions, encounters obstacles, adapts, and continues. The human observer reviews the aggregate output, not individual steps.

The efficiency gain is real. A well-constructed loop running against a well-specified task list can accomplish hours of repetitive work – documentation, test generation, systematic refactoring – without human supervision.

The Compounding Risk

A loop amplifies both the quality of its instructions and the defects within them. If a misunderstanding exists in the task specification, the loop will execute all subsequent tasks consistently with that misunderstanding. The error doesn't self-correct. It accumulates.

This is why loops are inappropriate for ambiguous, underspecified, or creatively demanding tasks. The absence of human review between steps removes the checkpoints that catch drift early.

The practical advice from practitioners who have learned this: build without loops first. Develop a repertoire of projects in which you have reviewed each plan, approved each edit, and understood each output. Build the judgment needed to distinguish a well-specified task from an underspecified one. Only then introduce autonomous loops, and only for the class of tasks – well-bounded, repetitive, clearly defined – that they serve well.

Tasks Where Loops Are Appropriate

  • Adding documentation to a systematically defined set of functions

  • Generating tests for components with clear, specified behavior

  • Applying a defined refactor pattern across a consistent set of files

  • Running a specified analysis against a set of inputs and recording results

Tasks Where Loops Are Not Appropriate

  • Building new features where scope or behavior is not fully defined

  • Architecture or design work requiring creative decision-making

  • Any task where the acceptability of the result is not specifiable in advance

  • Work in unfamiliar codebases where unexpected conditions are likely

The loop is a tool for known problems. It is not a substitute for understanding or oversight.

What an Autonomous Loop Looks Like — A Concrete Example

The best way to understand the difference between a supervised workflow and an autonomous loop is to see the same task done both ways.

The task: Add JSDoc documentation comments to every function in a codebase's utils/ directory. There are twelve utility files, each containing between three and ten functions.

Without a loop — supervised, step-by-step:

You open a session and say:

Code Snippet

Document all functions in utils/dateFormatters.js with JSDoc comments.
For each function, include: @param (name, type, description) for each parameter,
@returns (type, description), and a one-line @description.
Show me the diff before applying changes.

Claude produces the documentation, you review the diff, approve it. Then you repeat the process for utils/currencyUtils.js, and so on across all twelve files. You review every change as it happens.

This approach is appropriate when you are unfamiliar with the codebase, when the functions have unclear behavior that requires interpretation, or when you want to catch any misunderstanding early.

The cost: twelve back-and-forth cycles, each requiring your attention.

With a loop — autonomous, batch execution:

You have already supervised several files and confirmed that Claude is interpreting the functions correctly and producing clean JSDoc. The pattern is clear, the behavior is consistent, and the same operation applies uniformly across all files. Now you introduce the loop:

Code Snippet

You are going to add JSDoc documentation to every function in the utils/ directory.

Rules:
- For each function, add: @description (one line), @param (name, type, description)
  for each parameter, and @returns (type, description)
- Do not change any logic — only add documentation comments
- Do not modify any files outside of utils/
- Process one file at a time, in alphabetical order
- After completing all files, output a summary: which files were modified,
  and how many functions were documented in total

Begin with utils/analyticsHelpers.js and proceed through all files in utils/
without stopping for approval between files. Apply changes to each file before
moving to the next.

Claude works through all twelve files autonomously. You review the aggregate output when it finishes: a summary of changes made, which you can verify with a git diff.

What made the loop safe here:

  • The task was repetitive and uniform – the same operation applied to each file

  • You had already verified Claude's judgment on a representative sample

  • The specification was complete – there was no ambiguity about what "done" meant for each function

  • The scope was bounded – "do not modify any files outside of utils/"

  • The operation was additive only – "do not change any logic"

Change any of these conditions and the loop becomes riskier. An unfamiliar codebase, an ambiguous definition of done, or a task that requires creative judgment means supervised execution is the right approach.

Chapter 21: Code Review, Security, and Verification

Chapter 21 Header

The capabilities of Claude Code don't eliminate the requirement for verification. AI-generated code is held to the same standards as any other code. And those standards require review.

Known Categories of Failure

Claude Code generates code based on patterns learned during training. Across a wide range of tasks, this produces correct, functional, secure output. In a defined set of conditions, it does not.

  1. API hallucination: Claude may reference functions, parameters, or library versions that don't exist or have changed since its training data was collected. This is most common in libraries that evolve rapidly.

  2. Edge case omission: Claude generates implementations that handle the primary flow correctly and may not fully address boundary conditions – empty inputs, null values, network failures, malformed data.

  3. Security vulnerability introduction: Common vulnerability classes – SQL injection, inadequate input sanitization, insecure random number generation, improper credential handling – can be present in generated code that passes visual inspection. These require deliberate security review to detect.

  4. Confident incorrectness: Claude presents output with consistent confidence regardless of its correctness. The tone of a response is not a reliable indicator of its accuracy.

The Verification Standard

Here are some important ways you can review Claude's output to make sure it's up to your standards and security protocols:

  1. Read the code. Not exhaustively, but substantively. Understand what each significant section does. Could you explain it? Is the logic consistent with your stated requirements?

  2. Test the behavior. Manually exercise the functionality. Test the primary flow. Test the edges. Does it behave correctly when inputs are missing? When values are at their extremes? When dependencies are unavailable?

  3. Use automated verification. Request that Claude generate tests for the code it writes. Ask for coverage that includes edge cases explicitly. Automated tests are not a substitute for code review, but they catch regressions systematically.

  4. Apply heightened scrutiny to sensitive domains. Authentication, authorization, payment processing, medical data handling, privacy-related data storage – these areas require security expertise and careful review beyond what automated checks provide.

Claude Code Security

Anthropic has released Claude Code Security, a capability in preview as of 2026 that scans codebases for known vulnerability patterns and generates proposed corrections.

This represents the direction of security tooling: integrated, automated, and AI-assisted. For production systems, treat it as an additional layer, not a replacement for expert review.

The Continued Role of Writing Code

Experience across the Claude Code community consistently confirms: developers who write some code themselves, rather than delegating all implementation, maintain significantly better understanding of their systems.

This understanding is not incidental. It's what allows correct review of Claude's output. It's what surfaces subtle errors that are invisible to anyone without domain knowledge. And it's what produces systems that remain maintainable when the context of their creation is no longer fresh.

Use Claude Code to eliminate mechanical overhead: the boilerplate, the repetitive patterns, the documentation that takes time but requires no judgment. Don't use it to replace engagement with the system you are building. That engagement is where your expertise lives, and where the quality of the system is ultimately determined.

Chapter 22: Starter Project Blueprints

Chapter 22 Header

The following six project blueprints provide complete specifications – technology choices, directory structure, feature list, and implementation sequence – ready to hand directly to Claude Code. Each is designed for a specific stage of development competency and a specific class of use case.

These are not toy examples. They are real projects that produce genuinely useful software, chosen because they introduce important patterns in a controlled scope.

Blueprint 1: Personal Homepage

Appropriate for: Absolute beginners. First session with Claude Code.

What it teaches: HTML/CSS file structure, dark-themed UI, responsive layout, link components.

Technology: HTML5, CSS3, no JavaScript required.

Directory structure:

Code Snippet

my-homepage/
  index.html
  style.css
  assets/
    avatar.jpg       (add your own photo)

Prompt to give Claude Code:

Code Snippet

Build a personal homepage. Specifications:

1. Single-page HTML/CSS site — no JavaScript, no frameworks
2. Sections: hero with name and one-line description, short bio (2–3 sentences),
   links section with icons for LinkedIn, GitHub, and Twitter/X, footer with year
3. Design: dark background (#0f0f13), light body text (#e2e2e2),
   accent color (#6366f1 — indigo), sans-serif typography (Inter via Google Fonts)
4. Responsive: readable and clean on both desktop and mobile
5. File structure: index.html and style.css only

Placeholder text is fine for bio — I will replace it. Use placeholder links (#)
for the social links — I will update them.

Enter Plan Mode first. Show me the plan before writing any files.

What to verify after it builds:

  • Open in browser – does it look correct?

  • Resize the window to mobile width – does the layout adapt?

  • Check that all links exist (even as placeholders)

  • View the HTML source – can you understand the structure?

Blueprint 2: Task Manager with localStorage

Appropriate for: Early intermediate. First application with state and interactivity.

What it teaches: JavaScript DOM manipulation, localStorage persistence, CRUD patterns, event handling, filtering.

Technology: HTML5, CSS3, vanilla JavaScript. No dependencies. No build step.

Directory structure:

Code Snippet

task-manager/
  index.html
  style.css
  app.js
  components/
    TaskList.js
    TaskForm.js
    TaskFilter.js
  utils/
    storage.js      (localStorage read/write)
    dateUtils.js    (formatting helpers)

Prompt to give Claude Code:

Code Snippet

Build a task manager application. Full specification:

FEATURES:
1. Create task: title (required), description (optional), due date,
   priority (Low / Medium / High)
2. Display tasks as cards in a list, sorted by due date ascending
3. Mark task complete — completed tasks display with strikethrough and 50% opacity
4. Delete task with confirmation
5. Filter tasks: by status (All / Active / Completed), by priority (All / Low / Medium / High)
6. All data persists in localStorage — survives page refresh

TECHNICAL REQUIREMENTS:
- Vanilla JavaScript, ES6 modules
- File structure as specified: index.html, style.css, app.js,
  components folder with TaskList.js / TaskForm.js / TaskFilter.js,
  utils folder with storage.js and dateUtils.js
- No external libraries, no frameworks, no build step
- storage.js must abstract all localStorage access — no other file reads/writes localStorage directly

DESIGN:
- Clean light theme, comfortable whitespace
- Cards with subtle shadow and hover state
- Priority levels: low = blue, medium = amber, high = red (use colored left border on card)
- Responsive for mobile and desktop

CLAUDE.md content to follow: all localStorage access through storage.js only.
No inline styles — all styling through style.css.

Enter Plan Mode. Show me the complete file tree and implementation plan
before writing anything.

What to verify after it builds:

  • Create several tasks with different priorities and due dates

  • Verify they sort by due date correctly

  • Mark some complete – verify visual state

  • Refresh the page – verify data is preserved

  • Test filters – each combination should produce correct results

  • Delete a task – verify confirmation step works

Blueprint 3: API-Connected Data Dashboard

Appropriate for: Intermediate. First project involving an external API and dynamic data display.

What it teaches: Fetch API, async/await, loading states, error handling, structured data display.

Technology: HTML5, CSS3, vanilla JavaScript. Uses a free public API with no authentication required.

The API used: Open-Meteo weather API (free, no key required).

Directory structure:

Code Snippet

weather-dashboard/
  index.html
  style.css
  app.js
  services/
    weatherApi.js   (all API calls isolated here)
  components/
    CurrentWeather.js
    ForecastCard.js
    LocationSearch.js
  utils/
    formatters.js   (unit conversion, date formatting)

Prompt to give Claude Code:

Code Snippet

Build a weather dashboard using the Open-Meteo API (https://open-meteo.com).
No API key required. Full specification:

FEATURES:
1. Location search: user types a city name, app geocodes it using
   Open-Meteo's geocoding API and retrieves weather data
2. Current conditions display: temperature (Celsius), feels-like, humidity,
   wind speed, weather description with an icon (use Unicode weather emoji)
3. 7-day forecast: one card per day showing high/low temps and condition
4. Loading state: visible spinner while data is fetching
5. Error state: clear message if location not found or API fails
6. Last searched location persists in localStorage on refresh

TECHNICAL REQUIREMENTS:
- All API calls must go through services/weatherApi.js — no fetch() calls elsewhere
- Async/await throughout — no .then() chaining
- formatters.js handles all unit formatting and date display
- Graceful error handling: network failures and invalid locations display
  user-readable messages (never raw error objects)

API REFERENCE:
- Geocoding: https://geocoding-api.open-meteo.com/v1/search?name={city}&count=1
- Weather: https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}
  &current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code
  &daily=temperature_2m_max,temperature_2m_min,weather_code
  &timezone=auto&forecast_days=7

DESIGN: clean card-based layout, dark theme, readable type hierarchy.

Enter Plan Mode. Describe the complete data flow before writing code:
how a user search triggers the API chain and populates each component.

What to verify after it builds:

  • Search for a known city – does it return weather data?

  • Search for a nonexistent place – does it show a clean error?

  • Disconnect your network and search – does it handle the failure gracefully?

  • Refresh the page – does the last location reload?

Blueprint 4: Full-Stack Notes Application

Appropriate for: Intermediate-advanced. First project with a real backend and database.

What it teaches: Node.js/Express server, SQLite database, REST API design, client-server separation, CRUD at every layer.

Technology: Node.js, Express, better-sqlite3 (synchronous SQLite binding), HTML/CSS/vanilla JS frontend served statically.

Directory structure:

Code Snippet

notes-app/
  server/
    index.js          (Express app setup)
    db/
      database.js     (SQLite connection and migrations)
      notesRepo.js    (all database queries for notes)
    routes/
      notes.js        (REST routes for /api/notes)
    middleware/
      errorHandler.js
  client/
    index.html
    style.css
    app.js
    services/
      notesApi.js     (all fetch calls to the backend)
    components/
      NoteEditor.js
      NoteList.js
  package.json
  .env

Prompt to give Claude Code:

Code Snippet

Build a full-stack notes application. Complete specification:

BACKEND (Node.js + Express + SQLite):
1. Express server running on port 3001
2. SQLite database via better-sqlite3 package
3. Notes table: id (integer primary key autoincrement), title (text),
   content (text), created_at (datetime), updated_at (datetime)
4. REST API:
   - GET /api/notes — return all notes, ordered by updated_at descending
   - GET /api/notes/:id — return single note
   - POST /api/notes — create note, return created note
   - PUT /api/notes/:id — update note, return updated note
   - DELETE /api/notes/:id — delete note, return 204
5. Database initialization: create table if not exists on server start
6. Error handling middleware: catch all unhandled errors, return JSON error response

FRONTEND (HTML/CSS/vanilla JS):
1. Three-panel layout: sidebar (note list), editor (active note), empty state
2. Click a note in the sidebar to open it in the editor
3. New Note button creates an empty note and opens it immediately
4. Auto-save: debounce saves to the API 1 second after the user stops typing
5. Delete button on active note with confirmation
6. Note list shows title and first line of content as preview, plus updated date

CONVENTIONS (enforce in CLAUDE.md):
- All database access through notesRepo.js — no SQL in route files
- All API calls through client/services/notesApi.js — no fetch() elsewhere in frontend
- All routes return JSON — no HTML from the API

Enter Plan Mode. Show me the complete architecture: how data flows
from the database through the API to the UI and back on save.

What to verify after it builds:

  • Start the server: node server/index.js

  • Create a note – does it appear in the sidebar?

  • Edit it – does it save automatically?

  • Restart the server – is the note still there?

  • Delete a note – is it removed from the list?

  • Test with the network tab open – are the API calls correct?

Blueprint 5: CLI Automation Tool

Appropriate for: Developers with command-line comfort. Introduction to scriptable tools.

What it teaches: Command-line argument parsing, file system automation, structured output, practical tooling.

The tool built: A project scaffolding tool – given a project type argument, it generates a directory structure with starter files.

Technology: Node.js, commander (CLI argument library), fs-extra.

Directory structure:

Code Snippet

scaffold-tool/
  src/
    index.js          (entry point, argument definitions)
    commands/
      create.js       (scaffold a new project)
      list.js         (list available templates)
    templates/
      web-basic/      (template directory structure)
      node-api/
      react-app/
    utils/
      fileSystem.js   (file/directory operations)
      logger.js       (colored console output)
  package.json
  README.md

Prompt to give Claude Code:

Code Snippet

Build a Node.js command-line scaffolding tool. Full specification:

PURPOSE: Running `scaffold create <template-name> <project-name>` generates
a new project directory with a starter file structure.

COMMANDS:
1. scaffold create <template> <name>
   - Creates a new directory named <name> in the current working directory
   - Copies the corresponding template into it
   - Replaces the placeholder {{PROJECT_NAME}} in all template files with <name>
   - Prints a success message with next steps
2. scaffold list
   - Lists all available templates with a one-line description of each

TEMPLATES TO INCLUDE (create each as an actual template directory with starter files):
- web-basic: index.html, style.css, app.js, README.md
- node-api: server.js, routes/index.js, package.json with express, README.md
- react-app: package.json with react/vite, src/App.jsx, src/main.jsx, index.html

TECHNICAL REQUIREMENTS:
- commander package for argument parsing
- fs-extra for file system operations (not native fs)
- All file/directory operations through utils/fileSystem.js
- Colored console output (green = success, red = error, blue = info)
- If the target directory already exists, exit with a clear error — do not overwrite
- Executable via `npx scaffold` (set up package.json bin entry)

Enter Plan Mode. Show me how the create command flow works
end-to-end before writing any files.

What to verify after it builds:

  • Run node src/index.js list – are templates listed?

  • Run node src/index.js create web-basic my-test-project – does the directory appear?

  • Check that {{PROJECT_NAME}} was replaced throughout the generated files

  • Run the same command again – does it reject the duplicate?

Blueprint 6: Internal Team Tool

Appropriate for: Advanced intermediate. First project meant for real use by other people.

What it teaches: User-facing product thinking, data validation, shared state, production-readiness concerns.

The tool built: A team standup tracker – team members log daily standups (what they did, what they are doing, any blockers), and the tool displays a historical view per person.

Technology: Node.js, Express, SQLite, vanilla JS frontend.

Directory structure:

Code Snippet

standup-tracker/
  server/
    index.js
    db/
      schema.sql      (initial table definitions)
      database.js
      repos/
        standupsRepo.js
        usersRepo.js
    routes/
      standups.js
      users.js
    middleware/
      validate.js     (input validation)
      errorHandler.js
  client/
    index.html
    style.css
    app.js
    services/
      api.js
    components/
      StandupForm.js
      TeamView.js
      PersonHistory.js
  package.json

Prompt to give Claude Code:

Code Snippet

Build a team standup tracker. Complete specification:

CONTEXT: A small team (3–10 people) uses this tool to log and view
daily standups. Each standup has three fields: yesterday, today, blockers.

DATABASE SCHEMA:
- users: id, name, email (unique), created_at
- standups: id, user_id (FK), date (date type, one per user per day),
  yesterday (text), today (text), blockers (text, nullable), created_at

BACKEND API:
- GET /api/users — list all users
- POST /api/users — create user (name + email, validate uniqueness)
- GET /api/standups?date=YYYY-MM-DD — all standups for a given date, with user data joined
- GET /api/standups/user/:userId — last 14 days of standups for one user
- POST /api/standups — submit standup (userId, date, yesterday, today, blockers)
  — if standup for that user+date already exists, update it (upsert)

FRONTEND:
- Default view: today's standups for the whole team, one card per person
- Each card: user name, their standup fields, time submitted (or "Not submitted" if absent)
- Date navigation: prev/next day buttons to browse historical dates
- Submit standup: user selects their name from a dropdown, fills three fields, submits
- If the user already submitted today, the form pre-fills their existing standup for editing

VALIDATION (enforce server-side via validate.js middleware):
- yesterday and today: required, non-empty, max 1000 characters
- blockers: optional, max 1000 characters
- date: must be a valid date, not in the future
- userId: must reference an existing user

CONVENTIONS (write into CLAUDE.md before starting):
- All DB access through repos/ — no SQL in route files
- All validation through validate.js middleware — no validation logic in route handlers
- Routes return consistent JSON: { data: ... } on success, { error: ... } on failure

Enter Plan Mode. This is a multi-file, multi-layer project. Show me
the complete plan — architecture, data flow, file list, implementation sequence —
before writing a single file.

What to verify after it builds:

  • Create two or three users via the API (use a REST client or curl)

  • Submit standups for each – do they appear on the team view?

  • Navigate to yesterday – does it show an empty state correctly?

  • Submit a second standup for the same user and date – does it update rather than duplicate?

  • Submit with a missing "today" field – does the server reject it with a clear error?

Progression Through the Blueprints

These six projects exist on a deliberate progression:

Blueprint Key Pattern Introduced
1. Homepage File structure, static HTML/CSS
2. Task Manager JavaScript state, localStorage, component separation
3. API Dashboard External API, async/await, loading and error states
4. Notes App Backend + frontend, REST, database CRUD
5. CLI Tool Command-line interfaces, templating, file system automation
6. Team Tool Multi-user, validation layers, production-readiness

Each blueprint uses the patterns from the previous ones and adds a new dimension. Building them in sequence produces a developer who has encountered and solved the fundamental problems in each tier of application architecture – which is the foundation from which Claude Code can be directed most effectively.

Chapter 23: The Current Frontier of Claude Code

Chapter 23 Header

The pace at which Claude Code's capabilities are evolving makes any description of "current" features provisional. What follows is an account of the frontier as of early 2026.

Proactive Task Generation

Claude Code is beginning to generate actionable suggestions based on observed project signals – not merely executing tasks specified by the developer, but identifying tasks that warrant attention.

Boris describes the current state: Claude reads Slack feedback threads, examines GitHub issue trackers, reviews telemetry data, and surfaces suggestions with associated pull requests: "Here are a few things I can do. I've put up a couple PRs. Want to take a look?"

The developer reviews, approves, and directs. But the initiative no longer flows exclusively in one direction. The agent is beginning to participate in the question of what should be built, not only in the execution of what has been decided.

Beyond Software Development

Boris's assessment of the current state: "Coding is largely solved – at least the kind of coding I do." The frontier, accordingly, is expanding into adjacent domains.

The co-work product extends Claude Code's agentic capabilities – acting on tools, executing multi-step tasks, operating in browser environments – to general knowledge work. The target population is not only developers but anyone who works with digital tools: analysts, product managers, administrators, researchers.

For the development community, this expands the scope of what Claude Code can assist with beyond implementation into the full lifecycle of building a product: user research synthesis, product specification, market analysis, customer communication, project coordination.

Build for the Model Six Months Ahead

Boris's most actionable strategic guidance for those building on Claude Code: "Build for the model six months from now, not for the model of today."

Tool use capability, session duration, autonomous operation reliability – these dimensions improve on a predictable trajectory. A workflow designed for today's capability limits will be underpowered six months from now. A workflow designed at the edge of near-future capability will be effective exactly when it matters.

This requires accepting that the current product experience may be slightly ahead of the current model's reliable range. That gap closes. The developers and organizations that have already built the workflows when the model catches up will have a structural advantage.

Chapter 24: Software Engineering as a Discipline

Chapter 24 Header

This chapter addresses the question that underlies every chapter in this handbook: what does it mean to practice software engineering as an AI-assisted developer?

Three Levels of Engagement with Software

There are three main ways that software engineers engage with their projects:

  1. Programming is the translation of logic into code. It requires knowledge of syntax, libraries, patterns. It is the mechanical layer.

  2. Software engineering is the design and construction of reliable, maintainable systems. It requires judgment about architecture, testing strategy, operational concerns, and the long-term consequences of current decisions. Code is the medium, and the system is the product.

  3. Software architecture is the discipline of making structural decisions that determine what a system can become. It requires the ability to translate complex requirements into design, and to anticipate how a system will need to evolve.

Claude Code handles a significant portion of the programming layer. The engineering and architecture layers remain human responsibilities – and their importance, if anything, increases when the mechanical translation is delegated to an agent.

With Claude generating implementation rapidly, the quality of what gets built is almost entirely a function of the quality of the design guiding it.

What Remains Essential to Learn

So as a developer, what do you need to know these days? What should you focus on learning and improving?

How systems work

Understanding why a query index matters, what a foreign key constraint enforces, how a session token is validated, what a memory leak actually is – this knowledge is what allows you to direct Claude Code correctly and evaluate its output accurately. You don't need to implement these things by hand. You need to understand them.

Why design decisions exist

Separation of concerns, dependency inversion, input validation, layered access control aren't just bureaucratic conventions. They're solutions to recurring engineering problems. When you understand why a pattern exists, you can direct Claude to implement it correctly and catch deviations.

What quality looks like

Readable code. Consistent behavior. Graceful error handling. Clear interfaces. Adequate test coverage. These evaluations require judgment that is developed through experience, and that judgment is what separates software that works from software that lasts.

Taste and Standard

Boris speaks of "audacity and taste" as properties he looks for in the products his team builds – software that stops users in place, that solves a problem with an elegance that makes the solution feel inevitable. This standard cannot be delegated.

Taste is developed through sustained engagement with excellent work. Reading good software. Using well-designed products with a critical eye. Understanding what produces the feeling of quality, and being able to articulate that understanding precisely enough to direct Claude toward it.

The developers who produce remarkable software with Claude Code are not using it as a replacement for their own judgment. They are using it as an execution instrument for a vision that is entirely theirs.

Originality Over Imitation

Reproducing existing products – building another version of something that already exists – is a reasonable learning exercise. But it's a poor use of what Claude Code actually makes possible.

The barrier to building novel software has changed. A working prototype of a genuinely original idea can be produced in a weekend. The cost of exploring an unusual approach is dramatically lower than it was. The ability to try something that has never been tried, and to have a working version of it in hours, is new.

This creates an obligation to think more ambitiously about what to build, not to settle for the comfort of known ground. The question worth asking is not "what already exists that I could rebuild?" but "what should exist that does not?"

Chapter 25: A Structured Path Forward

Chapter 25 Header

This final chapter provides a concrete sequence for applying everything in this handbook.

Beginning This Week

The learning begins with a real project: not an exercise, not a tutorial reproduction, but something you intend to exist and potentially to use.

The size should be minimal. A personal homepage. A simple tool for a specific personal workflow. A landing page for an idea you have been carrying. The constraint is that it be real – that its completion produces something with actual value to you.

A structured first week:

Day 1: Install VS Code and the Claude Code extension. Configure your account. Ask Claude Code what it can do. Understand the interface.

Day 2: Define your first project. Write down five specific features. Make them as concrete as you can. Don't begin building yet.

Day 3: Use Plan Mode to discuss the project with Claude. Refine the plan until it reflects your intent accurately. Implement Feature 1.

Day 4: Test Feature 1 until you are confident it works correctly. Implement Feature 2.

Day 5: Refine the project. Ask Claude to review it against the original specification. Close any gaps.

Day 6: Share the project with someone who can give you honest feedback. Note the gap between what you built and what would have been better.

Day 7: Assess the week. What was harder than expected? Where did Claude's output surprise you? What would you do differently? What do you want to build next?

This cycle – one week, one small project, one complete iteration – produces more learning than any amount of reading about Claude Code.

The Development Sequence

Competency with Claude Code develops in stages. The sequence that I've found to be most effective and reasonable is:

Stage 1 — Orientation (Weeks 1–4): Web-based projects. Single-feature scope. Emphasis on learning to communicate with Claude Code precisely and to evaluate its output critically.

Stage 2 — Construction (Months 1–3): Multi-feature projects. Introduction to databases, APIs, and multi-file architecture. Emphasis on planning discipline and feature-by-feature verification.

Stage 3 — Professional Practice (Months 3–6): Full-stack applications. Deployment and production considerations. Multi-session workflows. MCP integrations. Emphasis on reliability, security, and maintainability.

Stage 4 — Advanced Operation (Months 6 and beyond): Parallel agent workflows. Autonomous loops for defined tasks. Custom Skills and project-level configuration. Open-source contribution and team-level Claude Code integration.

Each stage depends on the previous one. The temptation to skip ahead produces gaps that become expensive later.

Principles for Continued Growth

Start by analyzing every unexpected output. When Claude produces something different from what you expected – better or worse – understand why. This is how your model of Claude's behavior becomes accurate and your prompting becomes precise.

Make sure you read and understand the code Claude writes. Not line by line, but substantively. Passive acceptance of output that you do not understand produces a codebase you cannot maintain, direct, or explain.

You should also study software that you consider excellent. The standard against which you direct Claude is the standard you can articulate. Develop that standard through sustained exposure to well-built systems.

And don't neglect to write some code yourself. Maintain active engagement with implementation. The judgment that comes from building directly is what makes your direction of Claude effective.

On the Professional Implications

Boris Cherny's assessment of the professional landscape is direct: the role boundaries between software engineers, product managers, and designers are becoming less distinct.

The engineers, product managers, and designers on his team all write code. The value contributed by each is shifting from specialized technical execution toward the cross-disciplinary judgment that only comes from understanding the full system – user needs, technical constraints, business context, design quality.

His recommendation: cultivate breadth. The ability to reason across domains – to hold technical, product, and design considerations simultaneously – is what produces the most coherent decisions. AI handles the mechanical execution. The human provides the understanding.

The accumulation that matters is not the code you have written. It is the judgment you have developed through building real things, studying what others have built, and understanding the standards that separate work that endures from work that does not. That accumulation is not replicable by an AI agent. It is yours.

Appendix A: Claude Code Command Reference

Command Function
/help Display all available commands
/model Select active model (Sonnet, Opus, Haiku)
/permissions Review and modify Claude Code's permissions
/clear Reset the current session context
Shift+Tab (×2) Activate Plan Mode (terminal)
Ctrl+C Interrupt the current operation
@filename Reference a specific file in a prompt
/mcp Manage connected MCP servers

Appendix B: Standard Prompt Templates

Beginning a new feature:

Code Snippet

I want to implement the following feature:

[Feature title]
[Behavioral specification from PRD]

Enter Plan Mode and show me your proposed approach before writing any code.
Include: files to be affected, implementation sequence, data flow, edge cases.

Diagnosing a defect:

Code Snippet

A defect exists with the following characteristics:

Expected behavior: [description]
Observed behavior: [description]
Steps to reproduce: [sequence]
Relevant files: [if known]

Diagnose the cause and propose a correction. Do not implement until I have
reviewed your diagnosis.

Code review:

Code Snippet

Review the implementation you produced for [feature] against the following
criteria:

1. Are there edge cases not handled by the current implementation?
2. Are there security considerations that require attention?
3. Is the code readable and maintainable for someone encountering it without context?
4. Is error handling adequate for the failure modes this code may encounter?

Provide your assessment before I approve this feature as complete.

Context loading for a new session:

Code Snippet

New session. Begin by reading the following files in order:
1. PRD.md
2. CLAUDE.md
3. README.md

Confirm your understanding of the project's current state and identify
where we left off, so we can proceed without reconstructing context manually.

Appendix C: Reference Material

Anthropic Documentation

  • claude.ai — Account management and Claude.ai access

  • code.claude.ai — Claude Code installation and official documentation

Foundational Reading

For Those New to Web Development

Both provide sufficient foundation to understand what Claude Code is building and to evaluate its output critically.

Closing

This handbook was written in February 2026. The specifics of Claude Code – its features, its model capabilities, its interface – will continue to evolve. Some of what is written here will require updating within months.

The underlying principles will not.

The quality of what you build with Claude Code is determined by the quality of your planning, the precision of your communication, the rigor of your verification, and the clarity of your standards. These are properties of practice, not of tooling. They compound. They do not expire.

The LUNARTECH Fellowship: Bridging Academia and Industry

Addressing the growing disconnect between academic theory and the practical demands of the tech industry, the LUNARTECH Fellowship was created to bridge this talent gap. Far too often, aspiring engineers are caught in the “no experience, no job” loop, graduating with theoretical knowledge but unprepared for the messy reality of production systems.

To combat this systemic issue and halt the resulting brain drain, the Fellowship invests heavily in promising people, offering a transformative environment that prioritizes hands-on experience, mentorship, and real-world engineering over traditional degrees.

This 6-month, remote-first apprenticeship serves as an immersive odyssey from aspiring talent to AI trailblazer. Rather than paying to learn in isolation, Fellows work on live, high-stakes AI and data products alongside experienced senior engineers and founders. By tackling actual engineering challenges and building a concrete portfolio of production-ready work, participants acquire the job-ready skills needed to thrive in today’s competitive landscape.

If you're ready to break the loop and accelerate your career, you can explore these opportunities and start your journey here: https://www.lunartech.ai/our-careers.

Master Your Career: The AI Engineering Handbook

For those ready to transition from theory to practice, we have developed The AI Engineering Handbook: How to Start a Career and Excel as an AI Engineer. This comprehensive guide provides a step-by-step roadmap for mastering the skills necessary to thrive in the transformative world of AI in 2025. Whether you are a developer looking to break into a competitive field or a professional seeking to future-proof your career, this handbook offers proven strategies and actionable insights that have already empowered countless individuals to secure high-impact roles.

Inside, you will explore real-world industry workflows, advanced architecting methods, and expert perspectives from leaders at companies like NVIDIA, Microsoft, and OpenAI. From discovering the technology behind ChatGPT to learning how to architect systems that transform research into world-changing products, this eBook is your ultimate companion for career acceleration. You can download your free copy above and start mastering the future of AI.

About LunarTech Lab

“Real AI. Real ROI. Delivered by Engineers — Not Slide Decks.”

LunarTech Lab is a deep-tech innovation partner specializing in AI, data science, and digital transformation – across software products, data platforms, and AI-driven systems.

We build real systems, not PowerPoint strategies. Our teams combine product, data, and engineering expertise to design AI that is measurable, maintainable, and production-ready. We are vendor-neutral, globally distributed, and grounded in real engineering - not hype. Our model blends Western European and North American leadership with high-performance technical teams offering world-class delivery at 70% of the Big Four's cost.

How We Work — From Scratch, in Four Phases

1. Discovery Sprint (2–4 Weeks): We start with data and ROI – not assumptions to define what’s worth building and what’s not and how much it will cost you.

2. Pilot / Proof of Concept (8–12 Weeks): We prototype the core idea – fast, focused, and measurable.
This phase tests models, integrations, and real-world ROI before scaling.

3. Full Implementation (6–12 Months): We industrialize the solution — secure data pipelines, production-grade models, full compliance, and knowledge transfer to your team.

4. Managed Services (Ongoing): We maintain, retrain, and evolve the AI models for lasting ROI. Quarterly reviews ensure that performance improves with time, not decays. As we own LunarTech Academy, we also build customised training to ensure clients tech team can continue working without us.

Every project is designed from scratch, integrating product knowledge, data engineering, and applied AI research.

Why LunarTech Lab?

LunarTech Lab bridges the gap between strategy and real engineering, where most competitors fall short. Traditional consultancies, including the Big Four, sell frameworks, not systems – expensive slide decks with little execution.

We offer the same strategic clarity, but it’s delivered by engineers and data scientists who build what they design, at about 70% of the cost. Cloud vendors push their own stacks and lock clients in. LunarTech is vendor-neutral: we choose what’s best for your goals, ensuring freedom and long-term flexibility.

Outsourcing firms execute without innovation. LunarTech works like an R&D partner, building from first principles, co-creating IP, and delivering measurable ROI.

From discovery to deployment, we combine strategy, science, and engineering, with one promise: We don’t sell slides. We deliver intelligence that works.

Stay Connected with LunarTech

Follow LunarTech Lab on LunarTech NewsLetter and LinkedIn, where innovation meets real engineering. You’ll get insights, project stories, and industry breakthroughs from the front lines of applied AI and software development.

LunarTech Academy – Build the Future

If you are inspired by what Claude Code and AI-assisted development make possible and want to build the skills to operate at the frontier, consider joining academy.lunartech.ai. Our programs cover AI engineering, machine learning, data science, and applied development, equipping you with the practical, industry-ready expertise needed to build production systems, direct AI agents effectively, and ship software that actually works.

Whether you are a developer looking to level up, a founder who wants to build without a full engineering team, or a domain expert ready to turn your knowledge into working software - the LunarTech Academy is built for where you are going, not where you have been.



Read the whole story
alvinashcraft
51 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Deploying AI Models with Hugging Face

1 Share

Hugging Face has become the "operating system" of the modern AI revolution.

We just posted a comprehensive new course on the freeCodeCamp.org YouTube channel that will teach you about Hugging Face and how to deploy AI models on the platform.

This course is designed to take you from a curious tinkerer to an engineer capable of deploying real-world AI applications. Mohammed Abrah developed this course.

The course treats the Hugging Face ecosystem as a unified workflow, connecting models, datasets, and deployment tools into one coherent pipeline.

Here are the main topics covered in this course:

  • The Transformers Library: Deep dive into architectures like BERT, GPT-2, and DistilBERT.

  • Audio & Speech: Build pipelines for speech recognition and audio understanding.

  • Generative AI with Diffusers: Harness Stable Diffusion for sophisticated image generation workflows.

  • Multimodal Models: Explore the cutting edge where vision and language meet.

  • Interactive Demos with Gradio: Turn your models into user-friendly web applications in minutes.

  • Full Deployment via Spaces: Learn how to host your AI apps on the cloud for the world to see.

You can watch the full course now on the freeCodeCamp.org YouTube channel (7-hour watch).



Read the whole story
alvinashcraft
58 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Deploying the TX Text Control Document Editor from the Private NuGet Feed to Azure App Services (Linux and Windows)

1 Share
This tutorial shows how to deploy the TX Text Control Document Editor to Azure App Services using an ASP.NET Core Web App. The Document Editor is a powerful word processing component that can be used to create, edit, view and print documents in ASP.NET Core applications.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories