Noundry Blueprints

Production-ready code patterns and shortcuts for common use cases using Noundry libraries

Free Community Edition

You're viewing the free version of Noundry Blueprints. Paid subscribers get access to:

  • 500+ Additional Blueprints - Enterprise patterns, advanced architectures, and production examples
  • 🔧 Interactive Blueprint Builder - Generate custom code with our AI-powered tool
  • 📚 Full Source Code Access - Download complete working projects
  • 🎓 Video Tutorials - Step-by-step implementation guides
  • 💬 Priority Support - Direct access to Noundry engineers
  • 🚀 Early Access - New blueprints and features before public release
Showing blueprints

🛡️ Guardian - Authorization Policies

Attribute-based authorization with policies and claims

Security
Program.cs
using Noundry.Guardian;

// Add Guardian with custom policies
builder.Services.AddGuardian(options =>
{
    options.AddPolicy("AdminOnly", policy =>
        policy.RequireRole("Admin"));

    options.AddPolicy("CanEditPosts", policy =>
        policy.RequireClaim("Permission", "Posts.Edit"));

    options.AddPolicy("MinimumAge", policy =>
        policy.RequireAssertion(context =>
        {
            var ageClaim = context.User.FindFirst("age");
            return ageClaim != null && int.Parse(ageClaim.Value) >= 18;
        }));
});

// Use in controllers
[Authorize(Policy = "AdminOnly")]
public class AdminController : Controller
{
    [HttpPost]
    [Authorize(Policy = "CanEditPosts")]
    public async Task EditPost(int id, Post model)
    {
        // Only users with "Posts.Edit" claim can access
        return Ok();
    }
}
View Full Documentation →

🛡️ Guardian - Guard Clauses

Defensive programming with guard clauses

Validation
Product.cs
using Guardian;

public class Product
{
    public string Name { get; }
    public decimal Price { get; }
    public int StockQuantity { get; }

    public Product(string name, decimal price, int stockQuantity)
    {
        Name = Guard.Against.NullOrWhiteSpace(name);
        Price = Guard.Against.NegativeOrZero(price);
        StockQuantity = Guard.Against.Negative(stockQuantity);
    }
}

public class OrderService
{
    public async Task ProcessOrder(Order order, User user)
    {
        Guard.Against.Default(order.Id);
        Guard.Against.InvalidFormat(user.Email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
        Guard.Against.OutOfRange(order.Total, 0.01m, 10000m);

        await SaveOrderAsync(order);
    }
}
View Full Documentation →

⚠️ Assertive - Fluent Assertions

Readable test assertions with method chaining

Testing
UserTests.cs
using Noundry.Assertive;
using Xunit;

public class UserTests
{
    [Fact]
    public void TestUserCreation()
    {
        var user = new User { Name = "John Doe", Age = 30, Email = "john@example.com" };

        user.Assert()
            .IsNotNull()
            .Satisfies(u => u.Age >= 18, "User must be an adult")
            .Satisfies(u => !string.IsNullOrEmpty(u.Name), "User must have a name");
    }

    [Fact]
    public void TestCollectionOperations()
    {
        var numbers = new List { 1, 2, 3, 4, 5 };

        numbers.Assert()
            .IsNotEmpty()
            .HasCount(5)
            .Contains(3)
            .DoesNotContain(10);
    }

    [Fact]
    public void TestNumericAssertions()
    {
        42.Assert()
            .IsNotNull()
            .IsEqualTo(42)
            .IsOfType()
            .IsInRange(1, 100)
            .Satisfies(x => x > 0, "Number should be positive");
    }
}
View Full Documentation →

✅ Sod - Schema Validation

Zod-inspired fluent validation for .NET

Validation
UserValidator.cs
using Noundry.Sod;

// Define reusable schema
var userSchema = Sod.Object()
    .Field(u => u.Email,
        Sod.String().Email().Required())
    .Field(u => u.Age,
        Sod.Number().Min(18).Max(120))
    .Field(u => u.Username,
        Sod.String().Min(3).Max(20).Regex("^[a-zA-Z0-9_]+$"))
    .Field(u => u.Password,
        Sod.String().Min(8).Regex(@"^(?=.*[A-Z])(?=.*\d).*$"))
    .Field(u => u.Website,
        Sod.String().Url().Optional());

// Use in API controller
[HttpPost("register")]
public async Task Register(User user)
{
    var result = userSchema.Parse(user);

    if (!result.Success)
    {
        return BadRequest(result.Errors);
    }

    var validUser = result.Data;
    await _userService.CreateAsync(validUser);
    return Ok(new { message = "User created successfully" });
}
View Full Documentation →

🔐 DotEnvX - Encrypted Configuration

Secure environment variables with encryption

Config
Program.cs
using Noundry.DotEnvX.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

// Load .env with encryption & validation
builder.Configuration.AddDotEnvX(options =>
{
    options.Path = new[] { ".env", ".env.production" };
    options.EnvironmentSpecific = true;
    options.Required = new[]
    {
        "DATABASE_URL",
        "JWT_SECRET",
        "STRIPE_API_KEY"
    };
    options.EncryptionKey = Environment.GetEnvironmentVariable("DOTENVX_KEY");
});

// Use encrypted configuration
var jwtSecret = builder.Configuration["JWT_SECRET"]; // Automatically decrypted
var dbUrl = builder.Configuration["DATABASE_URL"];

// .env file example (values are encrypted):
// DATABASE_URL=postgresql://localhost/mydb
// JWT_SECRET="encrypted:BDb7t3QkTRp2..."
// STRIPE_API_KEY="encrypted:AES256:base64value"
View Full Documentation →

🔐 DotEnvX - CLI Tool

Manage encrypted secrets from the command line

Config
Terminal
# Install CLI tool
dotnet tool install --global Noundry.DotEnvX.Tool

# Generate encryption keypair
dotenvx keypair --save

# Set encrypted value
dotenvx set API_SECRET=supersecret --encrypt

# Set multiple values
dotenvx set API_KEY=key123 DEBUG=true PORT=3000

# List variables (masks sensitive values)
dotenvx list

# Get specific value
dotenvx get DATABASE_URL

# Encrypt all existing values
dotenvx encrypt

# Decrypt for viewing (doesn't save)
dotenvx decrypt

# Run command with env loaded
dotenvx run -- dotnet run
View Full Documentation →

🔌 Connector - Strongly-Typed API Client

Type-safe HTTP clients with Refit

API
IJsonPlaceholderApi.cs
using Refit;
using Noundry.Connector.Extensions;

// Define your API interface
public interface IJsonPlaceholderApi
{
    [Get("/users")]
    Task> GetUsersAsync(CancellationToken cancellationToken = default);

    [Get("/users/{id}")]
    Task GetUserAsync(int id, CancellationToken cancellationToken = default);

    [Post("/users")]
    Task CreateUserAsync([Body] User user, CancellationToken cancellationToken = default);
}

// Configure in DI
builder.Services.AddConnector(options =>
{
    options.BaseUrl = "https://jsonplaceholder.typicode.com";
    options.DefaultHeaders["User-Agent"] = "MyApp/1.0.0";
}, new TokenAuthenticationProvider("your-api-token"));

// Use in your service
public class UserService
{
    private readonly IJsonPlaceholderApi _api;

    public async Task> GetUsersByCityAsync(string city)
    {
        var users = await _api.GetUsersAsync();
        return users.Where(u => u.Address.City.Contains(city, StringComparison.OrdinalIgnoreCase));
    }
}
View Full Documentation →

🔌 Connector - Complete CRUD Operations

Full Create, Read, Update, Delete with type-safety

API
UserController.cs
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IJsonPlaceholderApi _api;

    public UsersController(IJsonPlaceholderApi api) => _api = api;

    // GET: api/users
    [HttpGet]
    public async Task>> GetUsers()
    {
        var users = await _api.GetUsersAsync();
        return Ok(users);
    }

    // GET: api/users/{id}
    [HttpGet("{id}")]
    public async Task> GetUser(int id)
    {
        var user = await _api.GetUserAsync(id);
        return Ok(user);
    }

    // POST: api/users
    [HttpPost]
    public async Task> CreateUser(User user)
    {
        var created = await _api.CreateUserAsync(user);
        return CreatedAtAction(nameof(GetUser), new { id = created.Id }, created);
    }

    // PUT: api/users/{id}
    [HttpPut("{id}")]
    public async Task> UpdateUser(int id, User user)
    {
        var updated = await _api.UpdateUserAsync(id, user);
        return Ok(updated);
    }

    // DELETE: api/users/{id}
    [HttpDelete("{id}")]
    public async Task DeleteUser(int id)
    {
        await _api.DeleteUserAsync(id);
        return NoContent();
    }
}
View Full Documentation →

🔌 Connector - OAuth 2.0 Authentication

GitHub API integration with OAuth

Security
Program.cs
using Noundry.Connector.Extensions;

// Configure OAuth 2.0 for GitHub
builder.Services.AddOAuthAuthentication(config =>
{
    config.ClientId = builder.Configuration["GitHub:ClientId"];
    config.ClientSecret = builder.Configuration["GitHub:ClientSecret"];
    config.TokenEndpoint = "https://github.com/login/oauth/access_token";
    config.AuthorizationEndpoint = "https://github.com/login/oauth/authorize";
    config.Scope = "repo user:email";
    config.GrantType = "authorization_code";
});

// Configure GitHub API client
builder.Services.AddConnector(options =>
{
    options.BaseUrl = "https://api.github.com";
    options.DefaultHeaders["Accept"] = "application/vnd.github.v3+json";
    options.DefaultHeaders["User-Agent"] = "MyGitHubApp/1.0.0";
});

// Use in service
public class GitHubService
{
    private readonly IGitHubApi _gitHubApi;

    public async Task> GetMyRepositoriesAsync()
    {
        var repos = await _gitHubApi.GetUserRepositoriesAsync("owner");

        // Use LINQ to analyze
        return repos
            .Where(r => r.Stars > 0)
            .OrderByDescending(r => r.Stars)
            .Take(10);
    }
}
View Full Documentation →

🎩 Tuxedo - Fluent Query Builder

LINQ-style database queries with type-safety

Data
ProductRepository.cs
using Noundry.Tuxedo;

public class ProductRepository
{
    private readonly IDbContext _db;

    // Basic queries
    public async Task> GetActiveProducts()
    {
        return await _db.Query()
            .Where(p => p.IsActive == true)
            .Where(p => p.Stock > 0)
            .OrderBy(p => p.Name)
            .ToListAsync();
    }

    // Pagination
    public async Task> GetPagedProducts(int page, int size)
    {
        return await _db.Query()
            .OrderBy(p => p.Name)
            .Skip((page - 1) * size)
            .Take(size)
            .ToListAsync();
    }

    // Insert/Update
    public async Task CreateAsync(Product product)
    {
        return await _db.InsertAsync(product);
    }

    public async Task UpdatePriceAsync(int id, decimal newPrice)
    {
        await _db.Update()
            .Set(p => p.Price, newPrice)
            .Set(p => p.UpdatedAt, DateTime.UtcNow)
            .Where(p => p.Id == id)
            .ExecuteAsync();
    }

    // Aggregations
    public async Task GetAveragePriceAsync()
    {
        return await _db.Query()
            .Where(p => p.IsActive)
            .AverageAsync(p => p.Price);
    }
}
View Full Documentation →

🎩 Tuxedo - Complex Joins & Aggregations

Type-safe joins with grouping and aggregations

Data
OrderRepository.cs
using Noundry.Tuxedo;

public class OrderRepository
{
    private readonly IDbContext _db;

    // Join with projection
    public async Task> GetProductsWithCategories()
    {
        return await _db.Query()
            .Join((p, c) => p.CategoryId == c.Id)
            .Select((p, c) => new ProductWithCategory
            {
                ProductName = p.Name,
                CategoryName = c.Name,
                Price = p.Price,
                InStock = p.Stock > 0
            })
            .ToListAsync();
    }

    // Multiple joins
    public async Task> GetOrderDetailsAsync()
    {
        return await _db.Query()
            .Join((o, c) => o.CustomerId == c.Id)
            .Join((o, c, p) => o.ProductId == p.Id)
            .Select((o, c, p) => new OrderDetails
            {
                OrderId = o.Id,
                CustomerName = c.Name,
                ProductName = p.Name,
                TotalAmount = o.Quantity * p.Price
            })
            .ToListAsync();
    }

    // Group by with aggregations
    public async Task> GetCategoryStatsAsync()
    {
        return await _db.Query()
            .GroupBy(p => p.CategoryId)
            .Select(g => new CategoryStats
            {
                CategoryId = g.Key,
                ProductCount = g.Count(),
                TotalValue = g.Sum(p => p.Price * p.Stock),
                AveragePrice = g.Average(p => p.Price)
            })
            .ToListAsync();
    }

    // Transactions
    public async Task TransferStock(int fromId, int toId, int quantity)
    {
        using var transaction = await _db.BeginTransactionAsync();
        try
        {
            await _db.Update()
                .Set(p => p.Stock, p => p.Stock - quantity)
                .Where(p => p.Id == fromId)
                .ExecuteAsync();

            await _db.Update()
                .Set(p => p.Stock, p => p.Stock + quantity)
                .Where(p => p.Id == toId)
                .ExecuteAsync();

            await transaction.CommitAsync();
        }
        catch
        {
            await transaction.RollbackAsync();
            throw;
        }
    }
}
View Full Documentation →

📥 Slurp - High-Performance CSV Ingestion

Fast CSV to database with schema inference

Data
DataImporter.cs / CLI
using Noundry.Slurp;

// As a Library
public class DataImporter
{
    private readonly ISlurpService _slurp;

    public async Task ImportCustomers(Stream csvStream)
    {
        var result = await _slurp.IngestCsvAsync(csvStream, options =>
        {
            options.TableName = "customers";
            options.Provider = DatabaseProvider.PostgreSQL;
            options.ConnectionString = _config["DATABASE_URL"];
            options.BatchSize = 5000;
            options.CreateTableIfNotExists = true;

            // Column mapping
            options.ColumnMappings = new Dictionary
            {
                ["First Name"] = "first_name",
                ["Last Name"] = "last_name",
                ["E-mail"] = "email"
            };

            // Transform data
            options.Transform = (row) =>
            {
                row["email"] = row["email"].ToLower();
                row["created_at"] = DateTime.UtcNow;
                return row;
            };
        });

        Console.WriteLine($"Imported {result.RowsProcessed} rows in {result.Duration}");
    }
}

// As CLI Tool
# Install
dotnet tool install --global Noundry.Slurp.Tool

# Import CSV
slurp customers.csv --provider postgres --connection $DB_URL --table customers

# Output:
✓ Analyzing CSV structure...
✓ Detected schema: 5 columns, 10,000 rows
⚡ Ingesting at 125,000 rows/sec
✅ Completed: 10,000 rows in 0.08 seconds
View Full Documentation →

🤖 Andy AI - Code Generation CLI

AI-powered Noundry code generation

Tools
Terminal
# Install Andy CLI
dotnet tool install -g Noundry.Andy

# Authenticate
andy auth login

# Generate Noundry.UI form
andy generate "form UI using Noundry.UI with fields: name, email, phone"

# Generate API endpoint
andy generate "CRUD API for Product entity with Guardian authorization"

# Generate Drizzle schema
andy generate "database schema for e-commerce with users, products, orders"

# Start interactive chat
andy chat

# Example output:
✅ Generated ProductForm.razor
✅ Generated ProductController.cs
✅ Generated IProductService.cs
✅ Applied Guardian authorization policies
✓ Code generation complete!

# Features:
- Uses CodeLlama-7B for generation
- Template-based with Noundry patterns
- Generates TagHelpers, Services, Controllers
- Local execution for security
- Real-time streaming responses
View Full Documentation →

🚀 ndts - Full-Stack TypeScript Setup

Initialize Bun + Hono + Drizzle project

Setup
Terminal
# Install and run ndts CLI
bunx @noundryfx/ndts-cli init

# Or with defaults (fast!)
bunx @noundryfx/ndts-cli init -y

# Interactive wizard prompts:
? Project name: my-app
? Select API architecture pattern:
  ❯ Direct API (DAPI) - Own database, cache, queues
    Backend for Frontend (BFF) - Wrap existing backend

# If DAPI:
? Select database: SQLite / PostgreSQL / MySQL
? Select email provider: Resend / SendGrid / SMTP / None
? Include caching? Y/n
? Include queues? Y/n
? Include blob storage? Y/n

# Generated structure:
my-app/
├── packages/
│   ├── server/           # Hono API (TypeScript)
│   │   ├── src/
│   │   │   ├── routes/       # API endpoints
│   │   │   ├── schema/       # Drizzle ORM schema
│   │   │   ├── middleware/   # Auth, logging
│   │   │   └── config/       # Database, cache
│   │   └── package.json
│   └── client/           # Pure HTML + TS (NO React!)
│       ├── pages/            # HTML files
│       ├── src/              # TypeScript modules
│       └── package.json

# Start development
cd my-app
bun install
bun run dev

# Server: http://localhost:3001
# Client: http://localhost:3000
View Full Documentation →

🎯 DAPI - Direct API Pattern

Direct database access with Drizzle ORM

API
packages/server/src/routes/users.ts
import { Hono } from 'hono';
import { db } from '../config/database';
import { users } from '../schema/users';
import { eq } from 'drizzle-orm';
import { auth } from '../middleware/auth';

const userRoutes = new Hono();

// Protect all routes
userRoutes.use('*', auth);

// GET /api/users - List all users
userRoutes.get('/', async (c) => {
  const allUsers = await db.select().from(users);
  return c.json(allUsers);
});

// GET /api/users/:id - Get single user
userRoutes.get('/:id', async (c) => {
  const id = parseInt(c.req.param('id'));
  const user = await db.select()
    .from(users)
    .where(eq(users.id, id))
    .get();

  if (!user) {
    return c.json({ error: 'User not found' }, 404);
  }

  return c.json(user);
});

// POST /api/users - Create user
userRoutes.post('/', async (c) => {
  const body = await c.req.json();

  const newUser = await db.insert(users)
    .values({
      name: body.name,
      email: body.email,
      createdAt: new Date()
    })
    .returning()
    .get();

  return c.json(newUser, 201);
});

// PUT /api/users/:id - Update user
userRoutes.put('/:id', async (c) => {
  const id = parseInt(c.req.param('id'));
  const body = await c.req.json();

  const updated = await db.update(users)
    .set({
      name: body.name,
      email: body.email,
      updatedAt: new Date()
    })
    .where(eq(users.id, id))
    .returning()
    .get();

  return c.json(updated);
});

// DELETE /api/users/:id
userRoutes.delete('/:id', async (c) => {
  const id = parseInt(c.req.param('id'));

  await db.delete(users)
    .where(eq(users.id, id));

  return c.json({ message: 'User deleted' });
});

export default userRoutes;
View Full Documentation →

🔌 BFF - Backend for Frontend Pattern

Wrap and enhance existing backend APIs

API
packages/server/src/services/UserService.ts
import { BaseService } from './BaseService.js';
import type { Context } from 'hono';

interface User {
  id: number;
  name: string;
  email: string;
}

export class UserService extends BaseService {
  // Call real backend, transform for frontend
  async getUsers(token?: string, c?: Context) {
    // Forward request to real backend (.NET, Java, Python, etc.)
    const users = await this.fetchFromBackend(
      '/api/users',
      token,
      { method: 'GET' }
    );

    // Transform data for frontend UI
    return users.map(user => ({
      ...user,
      displayName: user.name,
      initials: this.generateInitials(user.name),
      avatarColor: this.generateAvatarColor(user.id)
    }));
  }

  // Aggregate multiple backend calls
  async getUserDashboard(userId: number, token?: string) {
    const [user, stats, activity] = await Promise.all([
      this.fetchFromBackend(`/api/users/${userId}`, token),
      this.fetchFromBackend(`/api/users/${userId}/stats`, token),
      this.fetchFromBackend(`/api/users/${userId}/activity`, token)
    ]);

    // Combine and transform
    return {
      user,
      stats,
      recentActivity: activity.slice(0, 10),
      hasActivity: activity.length > 0
    };
  }

  // Helper methods
  private generateInitials(name: string): string {
    return name.split(' ')
      .map(n => n[0])
      .join('')
      .toUpperCase()
      .slice(0, 2);
  }

  private generateAvatarColor(id: number): string {
    const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'];
    return colors[id % colors.length];
  }
}

// Use in routes (packages/server/src/routes/users.ts)
import { Hono } from 'hono';
import { UserService } from '../services/UserService.js';
import { auth } from '../middleware/auth.js';

const app = new Hono();
const userService = new UserService(Bun.env.BACKEND_API_URL!);

app.use('*', auth);

app.get('/', async (c) => {
  const token = c.get('token');
  const users = await userService.getUsers(token, c);
  return c.json(users);
});

export default app;
View Full Documentation →

🔐 ndts - JWT Authentication

Secure authentication with JWT tokens

Security
packages/server/src/middleware/auth.ts
import { Context, Next } from 'hono';
import { verify } from 'hono/jwt';

export async function auth(c: Context, next: Next) {
  const authHeader = c.req.header('Authorization');

  if (!authHeader?.startsWith('Bearer ')) {
    return c.json({ error: 'Unauthorized' }, 401);
  }

  const token = authHeader.substring(7);

  try {
    const payload = await verify(token, Bun.env.JWT_SECRET!);
    c.set('userId', payload.userId);
    c.set('token', token); // For BFF forwarding
    await next();
  } catch (error) {
    return c.json({ error: 'Invalid token' }, 401);
  }
}

// Login route (packages/server/src/routes/auth.ts)
import { Hono } from 'hono';
import { sign } from 'hono/jwt';
import bcrypt from 'bcryptjs';

const authRoutes = new Hono();

authRoutes.post('/login', async (c) => {
  const { email, password } = await c.req.json();

  // Verify credentials (from database or backend API)
  const user = await findUserByEmail(email);
  if (!user || !await bcrypt.compare(password, user.password)) {
    return c.json({ error: 'Invalid credentials' }, 401);
  }

  // Generate JWT
  const token = await sign(
    { userId: user.id, email: user.email },
    Bun.env.JWT_SECRET!
  );

  return c.json({
    token,
    user: {
      id: user.id,
      name: user.name,
      email: user.email
    }
  });
});

authRoutes.post('/register', async (c) => {
  const { email, password, name } = await c.req.json();

  // Hash password
  const hashedPassword = await bcrypt.hash(password, 10);

  // Create user
  const user = await createUser({ email, password: hashedPassword, name });

  // Generate token
  const token = await sign(
    { userId: user.id, email: user.email },
    Bun.env.JWT_SECRET!
  );

  return c.json({ token, user }, 201);
});

export default authRoutes;
View Full Documentation →

🗃️ Drizzle - Type-Safe Database Queries

Modern TypeScript ORM with full type-safety

Data
packages/server/src/schema/products.ts
import { pgTable, serial, varchar, decimal, integer, timestamp } from 'drizzle-orm/pg-core';
import { eq, and, gte, lte, desc } from 'drizzle-orm';

// Define schema
export const products = pgTable('products', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  description: varchar('description', { length: 1000 }),
  price: decimal('price', { precision: 10, scale: 2 }).notNull(),
  stock: integer('stock').notNull().default(0),
  categoryId: integer('category_id').notNull(),
  createdAt: timestamp('created_at').defaultNow(),
  updatedAt: timestamp('updated_at').defaultNow()
});

// Use in routes
import { db } from '../config/database';

// Select with conditions
const activeProducts = await db.select()
  .from(products)
  .where(and(
    gte(products.stock, 1),
    lte(products.price, 100)
  ))
  .orderBy(desc(products.createdAt));

// Insert
const newProduct = await db.insert(products)
  .values({
    name: 'New Product',
    price: '29.99',
    stock: 50,
    categoryId: 1
  })
  .returning();

// Update
await db.update(products)
  .set({ stock: 100, updatedAt: new Date() })
  .where(eq(products.id, 1));

// Delete
await db.delete(products)
  .where(eq(products.id, 1));

// Complex joins
const productsWithCategories = await db.select({
  product: products,
  category: categories
})
  .from(products)
  .innerJoin(categories, eq(products.categoryId, categories.id))
  .where(eq(products.stock, 0));

// Generate migrations
// bun run db:generate
// bun run db:migrate
View Full Documentation →

🎨 Client - Pure HTML + TypeScript (NO React)

Modern web standards without framework overhead

Frontend
packages/client/pages/products.html + products.ts




  
  Products
  
  


  

Products

// packages/client/src/pages/products.ts import { api } from '../lib/api'; import { requireAuth } from '../lib/auth'; import '../styles/main.css'; requireAuth(); // Redirect if not logged in interface Product { id: number; name: string; price: string; stock: number; } async function loadProducts() { const products: Product[] = await api.get('/api/products'); const container = document.getElementById('products-list'); if (!container) return; container.innerHTML = products.map(product => `

${escapeHtml(product.name)}

$${product.price}

Stock: ${product.stock}

`).join(''); } function escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } (window as any).addToCart = async (productId: number) => { await api.post('/api/cart/add', { productId, quantity: 1 }); alert('Added to cart!'); }; loadProducts();
View Full Documentation →

No blueprints found

Try adjusting your search or filters

Ready to Accelerate Your Development?

These blueprints are production-ready patterns you can implement immediately. Upgrade to Pro for 500+ more examples and advanced features.