Thoth
A domain-specific language that generates TypeScript backend + React frontend from a single spec
Install / Use
/learn @abdufelsayed/ThothREADME
Thoth
<p align="center"> <strong>A Multitier Domain-Specific Language for Full-Stack Web Development</strong> </p>⚠️ DISCLAIMER
This is a proof-of-concept academic project created as part of a master's dissertation. It is not intended for production use and is currently unmaintained. Use at your own risk.
Table of Contents
- Overview
- What Makes Thoth Different
- Key Features
- Quick Example
- How It Works
- Language Features
- Getting Started
- Example Applications
- Project Structure
- Learn More
- Acknowledgments
Overview
Thoth is a statically-typed domain-specific language (DSL) that enables developers to build complete web applications—database schema, server API, and client UI—in a single, unified language. Write once in Thoth, and the compiler generates clean, human-readable TypeScript code for both your Node.js backend and React frontend.
The Problem
Traditional full-stack web development requires:
- Multiple programming languages (SQL, TypeScript/JavaScript, potentially others)
- Separate codebases for database migrations, backend APIs, and frontend UIs
- Manual synchronization of data models across tiers
- Extensive boilerplate code for CRUD operations, authentication, and real-time features
- Context switching between different paradigms and tools
The Solution
Thoth unifies the entire stack into a single declarative language where you define:
- Data models (compiled to PostgreSQL schema via Prisma)
- Queries (compiled to Express.js API routes with type-safe validation)
- Components (compiled to React components with TypeScript)
- Pages (compiled to React pages with routing)
- Authentication & Authorization (automatically generated and integrated)
What Makes Thoth Different
1. Truly Multitier
Unlike frameworks that simply share code between tiers, Thoth lets you declare your application's behavior at a higher level of abstraction. The compiler understands the relationships between database models, API endpoints, and UI components, generating all the glue code automatically.
2. Human-Readable Output
Thoth compiles to clean, idiomatic TypeScript code that you can read, understand, modify, and extend. No black-box magic—just well-structured React components, Express routes, and Prisma schemas.
3. Real-Time by Default
Every query automatically supports real-time updates via Server-Sent Events (SSE). Your UI components subscribe to data changes and update automatically—no WebSocket configuration needed.
4. Framework-Agnostic Design
While the default compiler targets Node.js + Express + Prisma + PostgreSQL on the backend and React + Vite on the frontend, the language itself is not tied to these technologies. The architecture supports alternative compilation targets.
5. Type Safety Across Tiers
Changes to your data models automatically propagate through your API contracts and UI components, maintaining type safety across the entire stack.
Key Features
✅ Declarative Data Modeling - Define models with fields, relationships, and attributes
✅ Automatic CRUD Generation - Create, Read, Update, Delete operations with minimal code
✅ Built-in Authentication - User signup, login, logout with session management
✅ Fine-Grained Authorization - Route-level and query-level permissions
✅ Real-Time Synchronization - SSE-based live updates across all connected clients
✅ Component System - Declare forms, buttons, and custom components with styling
✅ JSX-like Syntax - Familiar component rendering with template expressions
✅ TypeScript Integration - Leverage the entire npm ecosystem
✅ Custom Components - Write raw React/TypeScript when needed
✅ Client Dependencies - Specify npm packages to include in your project
Quick Example
Here's a simple todo application in Thoth:
app Todo {
title: "My Todo App",
auth: {
userModel: User,
idField: id,
usernameField: username,
passwordField: password,
onSuccessRedirectTo: "/",
onFailRedirectTo: "/login"
}
}
model User {
id Int @id
username String @unique
password String
tasks Task[]
}
model Task {
id Int @id
title String
isDone Boolean @default(false)
user User @relation(userId, id)
userId Int
}
@model(Task)
@permissions(IsAuth, OwnsRecord)
query<FindMany> getTasks {
search: [title, isDone]
}
@model(Task)
@permissions(IsAuth)
query<Create> createTask {
data: {
fields: [title],
relationFields: {
user: connect id with userId
}
}
}
component<Create> TaskForm {
actionQuery: createTask(),
formInputs: {
title: {
input: {
type: TextInput,
placeholder: "New task...",
isVisible: true
}
},
user: {
input: {
type: RelationInput,
isVisible: false,
defaultValue: connect id with LoggedInUser.id
}
}
},
formButton: {
name: "Add Task",
style: "bg-blue-500 text-white px-4 py-2 rounded"
}
}
@route("/")
@permissions(IsAuth)
page Home {
render(
<div>
<h1>{"My Tasks"}</h1>
<TaskForm />
<TasksComponent />
</div>
)
}
This compiles to a complete full-stack application with PostgreSQL database, Express REST API, and React frontend—all with real-time synchronization.
How It Works
┌─────────────────┐
│ .thoth file │ ← Your application code
└────────┬────────┘
│
▼
┌─────────────────┐
│ Thoth Compiler │ ← OCaml-based compiler (lexer, parser, type checker, code generator)
└────────┬────────┘
│
├──────────────────┬──────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ TypeScript │ │ TypeScript │ │ Prisma Schema │
│ React Client │ │ Express API │ │ + Migrations │
│ (Vite) │ │ (Node.js) │ │ (PostgreSQL) │
└────────────────┘ └──────────────┘ └─────────────────┘
Compilation Pipeline:
- Lexical Analysis - Tokenizes the
.thothsource file - Parsing - Builds an Abstract Syntax Tree (AST)
- Type Checking - Validates models, queries, components, and pages
- Specification Generation - Creates intermediate representations for each tier
- Code Generation - Uses Jinja2 templates to generate TypeScript and Prisma code
- Output - Produces two directories:
server/andclient/with complete applications
Language Features
Models
Define your data schema with Prisma-inspired syntax:
model User {
id Int @id
email String @unique
username String
posts Post[]
createdAt DateTime @default(Now)
}
model Post {
id Int @id
title String
content String
author User @relation(authorId, id)
authorId Int
published Boolean @default(false)
}
Queries
Declare type-safe database operations:
@model(Post)
@permissions(IsAuth)
query<FindMany> getPosts {
search: [title, content, published]
}
@model(Post)
@permissions(IsAuth, OwnsRecord)
query<Update> updatePost {
where: id,
data: {
fields: [title, content, published]
}
}
@model(Post)
@permissions(IsAuth, OwnsRecord)
query<Delete> deletePost {
where: id
}
Supported query types: FindUnique, FindMany, Create, Update, Delete
Components
Build UI components declaratively:
Action Forms:
component<Create> PostForm {
actionQuery: createPost(),
formInputs: {
title: {
label: { name: "Title" },
input: {
type: TextInput,
placeholder: "Enter title"
}
},
content: {
label: { name: "Content" },
input: {
type: TextInput,
placeholder: "Write your post..."
}
}
},
formButton: {
name: "Publish",
style: "bg-green-500 text-white px-4 py-2"
}
}
Data Display Components:
component<FindMany> PostsList {
findQuery: getPosts() as posts,
onLoading: render(<div>{"Loading..."}</div>),
onError: render(<div>{"Error loading posts"}</div>),
onSuccess: render(
<>
[% for post in posts %]
<PostCard post={post} />
[% endfor %]
</>
)
}
Custom Components:
component<Custom> PostCard(post: Post) {
imports: [|
import { useState } from "react";
import { Post } from "@/types";
|],
fn: [|
const [expanded, setExpanded] = useState(false);
return (
<div className="border rounded p-4">
<h3>{post.title}</h3>
<button onClick={() => setExpanded(!expanded)}>
{expanded ? "Show less" : "Show more"}
</button>
{expanded && <p>{post.content}</p>}
</div>
);
|]
}
Pages
Define routes and page components:
@route("/")
@permissions(IsAuth)
page Home {
render(
<div>
<h1>{"Home Page"}</h1>
<PostsList />
</div>
)
}
@route("/login")
page Login {
render(
<div>
<h1>{"Login"}</h1>
<LoginForm />
</div>
)
}
Authentication
Built-in authentication configuration:
app MyApp {
title: "My Application",
auth: {
userModel: User,
idField: id,
isOnlineField: isOnline, // optional
lastActiveField: lastActive, // optional
usernameField: username,
passwordField: password,
onSuccessRedirectTo: "/",
onFailRedirectTo: "/login"
}
}
Special components: SignupForm, LoginForm, LogoutButton
Permissions
Control access at the route and query level:
IsAuth- User must be authenticatedOwnsRecord- User must own the record being accessed (checks foreign key relationships)
