Build your first full-stack TypeScript application in minutes
No global installation needed - use bunx to run directly
bunx @noundryfx/ndts-cli init
๐ก Note: Node.js is NOT required. Bun is a drop-in replacement that handles all runtime needs including CLI execution.
Follow these steps to create and run your first application
Run the CLI and follow the interactive wizard
bunx @noundryfx/ndts-cli init
You'll be asked to configure:
Change into your newly created project directory
cd my-app
Copy the environment template and configure your secrets
cp .env.example packages/server/.env
Edit packages/server/.env:
# Server
PORT=3001
NODE_ENV=development
# Database (SQLite)
DB_URL=file:local.db
# Auth
JWT_SECRET=your-super-secret-key-change-in-production
JWT_EXPIRES_IN=7d
# CORS
CORS_ORIGIN=http://localhost:3000
Launch both the API server and frontend with automatic Docker service startup
bun run dev
When you run bun run dev, Docker containers automatically start for your selected services:
๐ Server Running:
http://localhost:3001
API endpoints available
๐ป Frontend Running:
http://localhost:3000
Open in browser
Open your browser and interact with your new app
โ What you'll see:
๐งช Try it out:
Follow this guide to build a complete Customer Relationship Management system with contacts, products, and invoices using HonoX
Manage customer records with email, phone, company, and notes
Product catalog with pricing, SKU, and stock tracking
Full invoicing system with line items and status tracking
Create a new project with database support
bunx @noundryfx/ndts-cli init
# Project name: crm-system
# Database: SQLite (or PostgreSQL for production)
# Auth: Custom JWT
# Email: None (for simplicity)
# Caching: Yes
# Other features: As needed
Add CRM tables to your schema
Edit packages/server/src/schema/schema.ts:
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
// Contacts table
export const contacts = sqliteTable('contacts', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
email: text('email'),
phone: text('phone'),
company: text('company'),
address: text('address'),
notes: text('notes'),
createdAt: integer('created_at', { mode: 'timestamp' })
.$defaultFn(() => new Date())
});
// Products table
export const products = sqliteTable('products', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => users.id),
name: text('name').notNull(),
description: text('description'),
price: integer('price').notNull(), // in cents
sku: text('sku'),
stock: integer('stock').default(0)
});
// Invoices table
export const invoices = sqliteTable('invoices', {
id: integer('id').primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => users.id),
contactId: integer('contact_id').references(() => contacts.id),
invoiceNumber: text('invoice_number').notNull(),
status: text('status').notNull().default('draft'), // draft, sent, paid, overdue
subtotal: integer('subtotal').notNull(),
tax: integer('tax').default(0),
total: integer('total').notNull(),
issueDate: integer('issue_date', { mode: 'timestamp' }),
dueDate: integer('due_date', { mode: 'timestamp' })
});
Create database tables from your schema
cd crm-system
bun run db:generate # Generate migration files
bun run db:migrate # Apply migrations to database
Add CRUD endpoints for contacts, products, and invoices
Create packages/server/src/routes/contacts.ts:
import { Hono } from 'hono';
import { auth } from '../middleware/auth';
import { db } from '../config/database';
import { contacts } from '../schema/schema';
import { eq, and } from 'drizzle-orm';
const app = new Hono();
// Protect all routes
app.use('*', auth);
// Get all contacts
app.get('/', async (c) => {
const userId = c.get('userId');
const results = await db.select()
.from(contacts)
.where(eq(contacts.userId, userId));
return c.json(results);
});
// Create contact
app.post('/', async (c) => {
const userId = c.get('userId');
const data = await c.req.json();
const [contact] = await db.insert(contacts)
.values({ ...data, userId })
.returning();
return c.json(contact, 201);
});
export default app;
๐ก Tip: Create similar routes for products.ts and invoices.ts
Build JSX routes with HonoX file-based routing
Create packages/client/app/routes/contacts/index.tsx:
export default function Contacts() {
return (
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">Contactsh1>
<div id="contacts-list" class="card">div>
div>
)
}
๐ก Tip: HonoX automatically creates the /contacts route from this file structure!
Run your CRM system and start customizing
bun run dev
Create .tsx files and HonoX automatically generates routes
app/routes/index.tsx
โ /
app/routes/about.tsx
โ /about
app/routes/records/index.tsx
โ /records
app/routes/records/[id].tsx
โ /records/:id
export default function Products() {
return (
<div class="container mx-auto p-8">
<h1 class="text-3xl font-bold">Productsh1>
<div class="grid grid-cols-3 gap-4">
{/* Product cards */}
div>
div>
)
}