The database is the application
Most business software spends its life moving data out of a database and then
putting it back. A query becomes a DTO, the DTO becomes JSON, the JSON becomes a
component, the component becomes a form, and the form becomes an UPDATE — with
validation rules written twice on the way, once in the browser and once on the
server, and a good chance they disagree.
TSQL.APP starts from a different premise: the data already lives in a database that can hold logic, enforce permissions and run transactions. Build the application there.
What that looks like
Here is a complete, working Hello World — a title, an input, a button, validation and feedback. All of it is T-SQL:
sqlDECLARE @Name NVARCHAR(MAX);
DECLARE @GreetButton NVARCHAR(MAX);
EXEC sp_api_modal_get_value @name = N'@Name', @value = @Name OUT;
EXEC sp_api_modal_get_value @name = N'@GreetButton', @value = @GreetButton OUT;
EXEC sp_api_modal_text @text = N'Hello World Example';
EXEC sp_api_modal_input @name = N'@Name',
@value = @Name OUT,
@placeholder = N'Enter your name';
EXEC sp_api_modal_button @name = N'@GreetButton',
@value = N'Greet',
@valueout = @GreetButton OUT,
@class = N'btn-primary';
IF @GreetButton IS NOT NULL
BEGIN
IF LEN(TRIM(ISNULL(@Name, N''))) = 0
EXEC sp_api_toast @text = N'Please enter your name', @class = N'btn-warning';
ELSE
BEGIN
DECLARE @Greeting NVARCHAR(MAX) = CONCAT(N'Hello, ', @Name, N'!');
EXEC sp_api_toast @text = @Greeting, @class = N'btn-success';
END
END
There is no HTML here, and no JavaScript. There is no controller, no route, no view model, no build step. The script declares what it wants on screen and reads back what the user did. That is the whole interface.
Two databases, and why your data stays put
The obvious objection is that nobody wants a framework rewriting their production schema. So TSQL.APP does not touch it.
An application is two databases. Your existing one — call it app — keeps
your tables exactly as they are. A second one, app_proj, holds the framework:
the sp_api_* procedures and the api_* metadata tables that describe cards,
fields, buttons and permissions. It reaches your data through synonyms.
Business data on one side, presentation and configuration on the other, and a clean line between them. Point the framework at a database that has been running for fifteen years and it will render it without asking you to migrate anything.
Everything else is a messenger
Around the database sit three small things, and none of them is allowed an opinion.
The .NET API flattens every HTTP request into a single four-column table and hands it to one stored procedure. It contains no domain knowledge at all — you can read the whole thing in an afternoon and learn nothing about the business it serves, which is the point.
The React client renders whatever named result sets come back. It does not know what a sales order is. It knows how to draw a list, a detail view and a modal, and it is told which to draw.
The dispatcher performs the IO that SQL Server cannot do for itself: email, HTTP fetches, Excel, PDF, SFTP, printing. Work is queued through Service Broker and picked up asynchronously.
So when someone asks how a feature works, the answer is always a stored procedure or an action script. Never C#, never JavaScript. There is exactly one place to look.
The part that surprises people
Here is the mechanism that makes a multi-step wizard work, and it is not what almost anyone guesses.
An action script has no resume point. It is re-executed from the top on every round trip. There are no coroutines, no saved instruction pointer, no server-side run state, and no step counter anywhere.
The answers the user has given so far travel as a JSON bag, out through the
browser and back again with the next request. Each collector — an input, a
button, a picker — prefers the posted answer over its own default. Before the
click, @GreetButton is NULL; after it, it holds the caption, and the IF
below it fires.
Which means "step 3 of 5" is never stored anywhere. It is emergent: step N is simply the first question whose name is not yet in the bag.
Once that clicks, a lot of the framework stops looking clever and starts looking inevitable. It is also why a script must be safe to run repeatedly from the top — easily the most important thing to internalise before writing a complicated one.
Where this goes
This blog is where the details get written down properly: how cards turn metadata into generated SQL, what the transaction and severity channel actually guarantee, the sharp edges worth knowing about before you find them yourself, and what we learn running all of this in production for real customers.
If you would rather see it than read about it, there is documentation and a programming reference online, and you can always book a demo.