Quick Start Guide
Quick Start Guide
Section titled “Quick Start Guide”Get TDI2 Running in 15 Minutes
Section titled “Get TDI2 Running in 15 Minutes”Transform your React app from props hell to service-oriented architecture with this complete e-commerce example.
🎯 What You'll Build
A complete ProductService with reactive state, automatic dependency injection, and zero-props components. By the end, you'll have a working product catalog with real-time updates.
💡 Why these design choices? See our Architecture Decisions for the reasoning behind TDI2’s approach.
Installation
Section titled “Installation”# Core packagesnpm install @tdi2/di-core @tdi2/vite-plugin-di valtio
# Or with bunbun add @tdi2/di-core @tdi2/vite-plugin-di valtio1. Configure Build Pipeline
Section titled “1. Configure Build Pipeline”import { defineConfig } from 'vite';import react from '@vitejs/plugin-react';import { diEnhancedPlugin } from '@tdi2/vite-plugin-di';
export default defineConfig({ plugins: [ react(), diEnhancedPlugin({ enableFunctionalDI: true, enableInterfaceResolution: true, enableLifecycleHooks: true, enableProfileSupport: true, enableConfigurationSupport: true, enableTestingSupport: true, verbose: true, // See transformation logs }) ],
// TypeScript configuration for decorators esbuild: { target: 'es2020' }});{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": false, "target": "ES2020", "module": "ESNext", "moduleResolution": "node" }}2. Create Your First Service
Section titled “2. Create Your First Service”Let’s build a ProductService for an e-commerce application:
export interface Product { id: string; name: string; price: number; description: string; category: string; imageUrl: string; stock: number;}
export interface ProductServiceInterface { state: { products: Product[]; selectedProduct: Product | null; loading: boolean; error: string | null; searchTerm: string; };
loadProducts(): Promise<void>; loadProduct(id: string): Promise<void>; searchProducts(term: string): void; clearSearch(): void;}import { Service, Inject, Profile, PostConstruct, PreDestroy } from '@tdi2/di-core';import type { ProductServiceInterface, Product } from '../interfaces/ProductServiceInterface';
@Service()@Profile("development", "production") // Available in both environmentsexport class ProductService implements ProductServiceInterface { state = { products: [] as Product[], selectedProduct: null as Product | null, loading: false, error: null as string | null, searchTerm: '', };
constructor( @Inject() private productRepository: ProductRepository, @Inject() private notificationService: NotificationService ) {}
@PostConstruct async initialize(): Promise<void> { // Called after dependency injection is complete console.log('ProductService initialized'); // Could pre-load critical data here }
@PreDestroy async cleanup(): Promise<void> { // Called before service destruction console.log('ProductService cleaning up'); // Clean up any resources, subscriptions, etc. }
async loadProducts(): Promise<void> { if (this.state.products.length > 0) return; // Smart caching
this.state.loading = true; this.state.error = null;
try { this.state.products = await this.productRepository.getProducts(); this.notificationService.showSuccess(`Loaded ${this.state.products.length} products`); } catch (error) { this.state.error = error.message; this.notificationService.showError('Failed to load products'); } finally { this.state.loading = false; } }
async loadProduct(id: string): Promise<void> { if (this.state.selectedProduct?.id === id) return;
this.state.loading = true; this.state.error = null;
try { this.state.selectedProduct = await this.productRepository.getProduct(id); } catch (error) { this.state.error = error.message; this.notificationService.showError('Product not found'); } finally { this.state.loading = false; } }
searchProducts(term: string): void { this.state.searchTerm = term; // Reactive filtering happens automatically in components }
clearSearch(): void { this.state.searchTerm = ''; }}3. Create Repository (Data Layer)
Section titled “3. Create Repository (Data Layer)”The repository pattern separates data fetching from business logic:
export interface ProductRepository { getProducts(): Promise<Product[]>; getProduct(id: string): Promise<Product>; searchProducts(term: string): Promise<Product[]>;}import { Service, Profile } from '@tdi2/di-core';
@Service()@Profile("production", "staging")export class ApiProductRepository implements ProductRepository { private readonly baseUrl = '/api/products';
async getProducts(): Promise<Product[]> { const response = await fetch(this.baseUrl); if (!response.ok) throw new Error('Failed to fetch products'); return response.json(); }
async getProduct(id: string): Promise<Product> { const response = await fetch(`${this.baseUrl}/${id}`); if (!response.ok) throw new Error('Product not found'); return response.json(); }
async searchProducts(term: string): Promise<Product[]> { const response = await fetch(`${this.baseUrl}/search?q=${encodeURIComponent(term)}`); if (!response.ok) throw new Error('Search failed'); return response.json(); }}@Service()@Profile("development", "production") // Available in both environmentsexport class NotificationService { showSuccess(message: string): void { // Integration with your notification system console.log('✅', message); // toast.success(message); }
showError(message: string): void { console.error('❌', message); // toast.error(message); }}4. Transform Your Component
Section titled “4. Transform Your Component”Here’s where the magic happens - write components with service injection:
import { Inject } from '@tdi2/di-core';import type { ProductServiceInterface } from '../services/interfaces/ProductServiceInterface';
interface ProductListProps { productService: Inject<ProductServiceInterface>;}
export function ProductList({ productService }: ProductListProps) { const { products, loading, error, searchTerm } = productService.state;
// Filter products based on search term (reactive!) const filteredProducts = products.filter(product => product.name.toLowerCase().includes(searchTerm.toLowerCase()) || product.category.toLowerCase().includes(searchTerm.toLowerCase()) );
useEffect(() => { productService.loadProducts(); }, []);
if (loading) return <div className="loading">Loading products...</div>; if (error) return <div className="error">Error: {error}</div>;
return ( <div className="product-list"> <div className="search-bar"> <input type="text" placeholder="Search products..." value={searchTerm} onChange={(e) => productService.searchProducts(e.target.value)} /> {searchTerm && ( <button onClick={() => productService.clearSearch()}> Clear Search </button> )} </div>
<div className="products-grid"> {filteredProducts.length === 0 ? ( <div className="no-products"> {searchTerm ? 'No products match your search' : 'No products available'} </div> ) : ( filteredProducts.map(product => ( <ProductCard key={product.id} productId={product.id} onSelect={() => productService.loadProduct(product.id)} /> )) )} </div> </div> );}interface ProductCardProps { productId: string; onSelect: () => void; productService: Inject<ProductServiceInterface>;}
export function ProductCard({ productId, onSelect, productService }: ProductCardProps) { const product = productService.state.products.find(p => p.id === productId);
if (!product) return null;
return ( <div className="product-card" onClick={onSelect}> <img src={product.imageUrl} alt={product.name} /> <h3>{product.name}</h3> <p className="price">${product.price}</p> <p className="description">{product.description}</p> <div className="stock"> {product.stock > 0 ? `${product.stock} in stock` : 'Out of stock'} </div> </div> );}5. Setup DI Provider
Section titled “5. Setup DI Provider”Configure the DI container and wrap your app with the provider:
import React from 'react';import { createRoot } from 'react-dom/client';import { DIContainer, DIProvider } from '@tdi2/di-core';import { DI_CONFIG } from './.tdi2/di-config'; // Auto-generated by Vite pluginimport App from './App';
// Create and configure the DI containerconst container = new DIContainer();container.loadConfiguration(DI_CONFIG);
createRoot(document.getElementById('root')!).render( <DIProvider container={container}> <App /> </DIProvider>);import { ProductList } from './components/ProductList';
function App() { return ( <div className="app"> <header> <h1>E-Commerce Product Catalog</h1> </header>
<main> <ProductList /> {/* No props needed - DI handles it! */} </main> </div> );}
export default App;What Happens During Transformation
Section titled “What Happens During Transformation”When you build your app, TDI2’s Vite plugin transforms your components:
Your Code (Input)
Section titled “Your Code (Input)”function ProductList({ productService }: { productService: Inject<ProductServiceInterface> }) { const { products, loading } = productService.state; // Component logic...}Generated Code (Output)
Section titled “Generated Code (Output)”function ProductList() { // TDI2-GENERATED: Automatic service injection const productService = useService<ProductServiceInterface>('ProductService');
// TDI2-GENERATED: Reactive state snapshots const productServiceSnap = useSnapshot(productService.state); const { products, loading } = productServiceSnap;
// Your original component logic (unchanged) // Component logic...}TDI2 automatically converts service props into useService hooks and adds reactive snapshots for optimal performance. Your production code contains zero DI abstractions!
Verification
Section titled “Verification”Test your setup by running the development server:
bun run devYou should see:
- Build logs showing TDI2 transformations (if verbose: true)
- Working product list with search functionality
- Reactive updates when you type in the search box
- No props drilling - components get data from services
Advanced Features You Can Now Use
Section titled “Advanced Features You Can Now Use”Environment-Specific Services
Section titled “Environment-Specific Services”// Development-only mock service@Service()@Profile("development")export class MockEmailService implements EmailServiceInterface { sendEmail(to: string, subject: string): Promise<void> { console.log(`Mock email to ${to}: ${subject}`); return Promise.resolve(); }}
// Production SMTP service@Service()@Profile("production")export class SmtpEmailService implements EmailServiceInterface { sendEmail(to: string, subject: string): Promise<void> { return this.smtpClient.send({ to, subject }); }}Configuration Classes
Section titled “Configuration Classes”@Configuration()export class DatabaseConfiguration { @Bean() @Profile("development") createDevDatabase(): DatabaseConnection { return new SqliteConnection('dev.db'); }
@Bean() @Profile("production") createProdDatabase(): DatabaseConnection { return new PostgresConnection(process.env.DATABASE_URL); }}Benefits You Just Gained
Section titled “Benefits You Just Gained”| Traditional React | Your New TDI2 App |
|---|---|
| Props drilling through multiple levels | Direct service injection |
| Manual state synchronization | Automatic reactive updates |
| Complex component testing | Simple service unit tests |
| Tight coupling between components | Loose coupling via interfaces |
| Performance optimization required | Automatic surgical re-renders |
| Environment configuration scattered | Centralized @Profile management |
| Manual lifecycle management | Automatic @PostConstruct/@PreDestroy |
Next Steps
Section titled “Next Steps”Essential Reading
Section titled “Essential Reading”- Service Patterns - Design robust, scalable services
- Component Guide - Transform existing components
- Testing Guide - Complete testing framework
Advanced Topics
Section titled “Advanced Topics”- Package Documentation - Complete feature reference
- Configuration Management - @Configuration and @Bean patterns
- Profile Management - Environment-based service activation
- Lifecycle Hooks - @PostConstruct and @PreDestroy patterns
Examples
Section titled “Examples”- Complete E-Commerce App - Working implementation
- Interactive Demos - Live code transformations
Troubleshooting
Section titled “Troubleshooting”Build Errors
Section titled “Build Errors”TypeScript decorator errors?
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": false }}Services not injecting?
- Ensure
@Service()decorator on your service class - Verify
Inject<InterfaceName>type annotation on component props - Check that interface name matches service class name pattern
- For environment-specific services, verify
@Profile()matches current environment
State not updating?
- Confirm Valtio is installed:
bun add valtio - Enable Valtio integration in plugin config
- Check browser console for TDI2 transformation logs
Missing dependencies?
- Ensure all
@Inject()dependencies have corresponding@Service()implementations - Check that profile-specific services are active in current environment
- Verify @Configuration classes are properly registered
- Check the browser console for DI container errors
Getting Help
Section titled “Getting Help”- Vite Plugin Documentation - Plugin configuration and troubleshooting
- GitHub Issues - Report bugs or ask questions
- Examples Repository - Working example applications
🎉 Congratulations! You’ve just built your first TDI2 application with reactive services, automatic dependency injection, and zero props drilling. Welcome to the future of React architecture!