Table of Contents

🧩 Add Your First Command

Let’s implement your first Potato command: CreateUserCommand. This command allows you to add new users to the tree structure under the root node.

πŸ“ Follow the Potato feature-folder layout:

Potato.Example.Application/Features/Users/Create/

πŸ’‘ Protip: Add a Usings.cs file to your project root to eliminate repetitive imports:

global using static LanguageExt.Prelude;
global using static Potato.PotatoModule;
global using LanguageExt;
global using Potato;
global using Potato.Examples.Domain;

πŸ“„ CreateUserCommand.cs

namespace Potato.Examples.Application;

/// <summary>
/// Command to create a new user node under the root.
/// </summary>
public record CreateUserCommand(string Username) : PotatoCommand<UserNode>;

πŸ“„ CreateUserCommand.Validator.cs

using FluentValidation;

namespace Potato.Examples.Application;

/// <summary>
/// Validator for the <see cref="CreateUserCommand"/>.
/// </summary>
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator()
    {
        RuleFor(x => x.Username)
            .NotEmpty()
            .MinimumLength(3)
            .MaximumLength(32);
    }
}

πŸ“„ CreateUserCommand.Handler.cs

using System.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Potato.Examples.Application;

/// <summary>
/// Handles the creation of a new <see cref="UserNode"/> in the tree.
/// </summary>
public class CreateUserCommandHandler
( 
    ILogger<CreateUserCommandHandler> logger,
    PotatoRepository<Node> repository
): PotatoCommandHandler<CreateUserCommand, UserNode>
{
   public override async Task<Either<PotatoFailure, UserNode>> Handle(CreateUserCommand command, CancellationToken cancellationToken)
   {
      try
      {
          using Activity? activity = Activity.Current?.Source.StartActivity();
          logger.LogCommandRecieved(command);
          var user = new UserNode(
                Id: Guid.NewGuid(),
                ParentId: RootNode.Instance.GetIdOrThrow(),
                Username: command.Username
            );

            return await repository.Create(user)
                .MapAsync(n => (UserNode)n)
                .HandlerFinished(logger, command);
      }
      catch (Exception e)
      {
          logger.LogCommandFailed(command, e);
          return UnexpectedFailure(e);
      }
   }
}

πŸ“„ CreateUserCommand.Extension.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Potato.Examples.Application;

namespace Microsoft.Extensions.DependencyInjection;

public static class CreateUserCommandExtension
{
    public static RouteHandlerBuilder MapCreateUser(this WebApplication app)
    {
        return app.MapPut("users/create", async ([FromBody] CreateUserCommand command, [FromServices] PotatoCommandExecutor executor) =>
        {
            return await executor.Execute<CreateUserCommand, UserNode>(command);
        });
    }
}

πŸ“Œ Register the route in Program.cs:

app.MapCreateUser();

πŸ“„ Potato.Example.Application.http

Use this HTTP request file to test the endpoint via REST Client in VS Code:

@host = http://localhost
@port = 5122

PUT {{host}}:{{port}}/users/create
Content-Type: application/json

{
  "username": "JohnDoe"
}

###

βœ… Summary

You now have:

  • βœ… A CreateUserCommand to add users to the tree
  • βœ… Validation rules via FluentValidation
  • βœ… A command handler that inserts the UserNode under the root
  • βœ… A minimal API endpoint to expose the command
  • βœ… A .http file for testing
  • βœ… A global failure handler for proper HTTP responses
  • βœ… Cleaner code using Usings.cs

➑️ Next: Create your first query to retrieve a user by ID.