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 Noundry.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.PrivateKey = Environment.GetEnvironmentVariable("DOTENV_PRIVATE_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 →

🚀 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
<!-- packages/client/pages/products.html -->
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Products</title>
 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@0.3.2/dist/basecoat.min.css">
 <link rel="stylesheet" href="/css/main.css">
</head>
<body class="min-h-screen bg-white/[0.02] p-8">
 <div class="max-w-6xl mx-auto">
 <div class="card">
 <div class="card-header">
 <h1 class="text-3xl font-bold">Products</h1>
 </div>
 <div class="card-body">
 <div id="products-list" class="grid grid-cols-1 md:grid-cols-3 gap-4"></div>
 </div>
 </div>
 </div>

 <script type="module" src="/src/pages/products.ts"></script>
</body>
</html>

// 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 => `
 <div class="card">
 <div class="card-body">
 <h3 class="text-xl font-bold">${escapeHtml(product.name)}</h3>
 <p class="text-2xl text-indigo-400">$${product.price}</p>
 <p class="text-sm text-zinc-500">Stock: ${product.stock}</p>
 <button
 class="btn btn-primary w-full mt-4"
 onclick="addToCart(${product.id})"
 >
 Add to Cart
 </button>
 </div>
 </div>
 `).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.