Tina4

Tina4 Ruby - Quick Reference#

🔥 Hot Tips

  • Route files live in src/routes/, templates in src/templates/, static files in src/public/
  • GET routes are public by default; POST/PUT/PATCH/DELETE require a token (use @noauth to override)
  • Return a Hash or Array from a route block and it auto-detects as JSON
  • Run tina4 serve to launch the dev server on port 7147
  • Chain .secure or .cache on any route for auth and caching

InstallationStatic WebsitesRoutingMiddlewareTemplatesSessionsSCSSEnvironmentsAuthenticationForms & TokensAJAXOpenAPIDatabasesDatabase ResultsMigrationsORMCRUDREST ClientTestingServicesWebsocketsQueuesWSDLGraphQLLocalizationHTML BuilderEventsLoggingCacheHealthDI ContainerError OverlayDev AdminCLIMCPFakeData

Installation#

bash
# Install the tina4 CLI once. Windows: irm https://tina4.com/install.ps1 | iexcurl -fsSL https://tina4.com/install.sh | shtina4 init ruby my-appcd my-apptina4 serve

The server starts on port 7147. One gem. No dependency tree. More details on project setup and customization.

Static Websites#

Put .html templates in ./src/templates and assets in ./src/public.

html
<!-- src/templates/index.html --><h1>Hello Static World</h1>

More details on static website routing.

Basic Routing#

ruby
Tina4::Router.get("/") do |request, response|  response.html "<h1>Hello Tina4 Ruby</h1>"endTina4::Router.post("/api/items") do |request, response|  name = request.body["name"] || ""  response.json({ name: name }, 201)endTina4::Router.get("/users/{id:int}") do |request, response|  id = request.params[:id]  response.json({ user_id: id })end

Drop route files in src/routes/. Tina4 discovers them at startup. Follow the links for basic routing and dynamic routing with typed parameters.

Middleware#

ruby
log_request = lambda do |request, response, next_handler|  $stderr.puts "#{request.method} #{request.path}"  next_handler.call(request, response)endTina4::Router.get("/api/data", middleware: "log_request") do |request, response|  response.json({ data: [1, 2, 3] })end# Apply middleware to a groupTina4::Router.group("/api/admin", middleware: "require_auth") do  Tina4::Router.get("/dashboard") do |request, response|    response.json({ page: "admin dashboard" })  endend

Follow the links for more on Middleware Declaration and Pattern Matching.

Template Rendering#

Put .html templates in ./src/templates and assets in ./src/public.

html
<!-- src/templates/greeting.html --><h1>Hello {{ name }}</h1>
ruby
Tina4::Router.get("/") do |request, response|  response.render("greeting.html", { name: "World" })end

Sessions#

Sessions start on their own. Every route handler receives request.session ready to use.

ruby
Tina4::Router.get("/session/set") do |request, response|  request.session.set("name", "Joe")  request.session.set("info", { list: ["one", "two", "three"] })  response.text "Session set."endTina4::Router.get("/session/get") do |request, response|  name = request.session.get("name", "Guest")  info = request.session.get("info", {})  response.json({ name: name, info: info })end

SCSS Stylesheets#

Drop files in ./src/public/scss, Tina4 compiles them to ./src/public/css.

scss
// src/public/scss/main.scss$primary: #2c3e50;body {  background: $primary;  color: white;}

More details on CSS and SCSS.

Environments#

The .env file sits at the project root. Tina4 reads it at startup.

TINA4_DEBUG=trueTINA4_PORT=7147TINA4_DATABASE_URL=sqlite:///data/app.dbTINA4_LOG_LEVEL=ALLTINA4_API_KEY=ABC1234
ruby
api_key = ENV["TINA4_API_KEY"] || "ABC1234"

Access env vars programmatically:

ruby
Tina4::Env.load_env                          # Load .env file (auto on server start)Tina4::Env.get_env("TINA4_DATABASE_URL")           # Get value or nilTina4::Env.get_env("PORT", "7147")           # Get value with defaultTina4::Env.has_env?("TINA4_DEBUG")           # true if setTina4::Env.require_env!("TINA4_DATABASE_URL")      # Raises KeyError if missingTina4::Env.truthy?(ENV["TINA4_DEBUG"])       # true for "true", "1", "yes"

Authentication#

Tina4 uses JWT tokens. Keys auto-generate in .keys/. GET routes are public. POST/PUT/PATCH/DELETE require a bearer token by default.

ruby
# Public login routeTina4::Router.post("/login") do |request, response|  token = Tina4::Auth.get_token({ user_id: 1, role: "admin" })  response.json({ token: token })end# Secured GET route (chain .secure)Tina4::Router.get("/api/profile") do |request, response|  response.json({ user: request.user })end.secure# Or use the @secured decorator# @securedTina4::Router.get("/api/account") do |request, response|  response.json({ account: request.user })end

HTML Forms and Tokens#

html
<form method="POST" action="/register">    <input name="email">    <button>Save</button></form>

More details on posting form data.

AJAX and frond.js#

Tina4 ships with frond.js, a zero-dependency JavaScript library for AJAX calls, form submissions, and real-time WebSocket connections.

More details on available features.

OpenAPI and Swagger UI#

Visit http://localhost:7147/swagger, available when TINA4_DEBUG=true.

ruby
# List all users# @description Returns all registered users# @tags Users# @query int $page Page number (default: 1)Tina4::Router.get("/api/users") do |request, response|  response.json({ users: [] })end

Follow the links for more on Configuration, Usage and Metadata.

Databases#

ruby
# Configured via .env (default: sqlite:///data/app.db)# Or create a connection in code:db = Tina4::Database.new("sqlite://app.db")db = Tina4::Database.new("postgres://localhost:5432/myapp", pool: 5)

Follow the links for more on Available Connections, Core Methods, Usage and Transactions.

Database Results#

ruby
result = db.fetch("SELECT * FROM users", [], limit: 3, skip: 1)array = result.to_a        # Array of hashesjson  = result.to_json     # JSON stringcsv   = result.to_csv      # CSV string

Looking at detailed Usage will deepen your understanding.

Migrations#

bash
tina4 migrate --create create_users_table
sql
-- src/migrations/20260313120000_create_users_table.sqlCREATE TABLE users (    id   INTEGER PRIMARY KEY AUTOINCREMENT,    name TEXT);
bash
tina4 migrate

Migrations have rollback support and status tracking.

ORM#

ORM models live in src/orm/. Tina4 auto-loads every .rb file in that directory.

ruby
class User < Tina4::ORM  integer_field :id, primary_key: true  string_field  :name  string_field  :email  table_name "users"enduser = User.newuser.name = "Alice"user.email = "alice@example.com"user.savefound = User.find(1)found.name = "Alice Wonder"found.save

The ORM handles insert-or-update in a single save call. See the Advanced Detail for the full picture.

CRUD#

ruby
Tina4::Router.get("/users/dashboard") do |request, response|  users = User.all  response.render("users/dashboard.html", { users: users })end

More details on how CRUD works.

Consuming REST APIs#

ruby
api = Tina4::API.new("https://api.example.com", auth_header: "Bearer xyz")result = api.get("/users/42")puts result.body

More details on POST bodies, authorization headers, and API responses.

Inline Testing#

Tina4 uses RSpec. Tests live in tests/. Every _spec.rb file is auto-discovered.

ruby
require "tina4"RSpec.describe "Math operations" do  it "adds numbers" do    expect(2 + 2).to eq(4)  endend

Run: tina4 test

Services#

ruby
class CacheWarmer < Tina4::Service  def run    Tina4::Log.info("Warming cache...")    # Your background work here    sleep 60  endendTina4::ServiceRunner.register(CacheWarmer.new)Tina4::ServiceRunner.start_all

Websockets#

Define WebSocket handlers the same way you define HTTP routes. The handler receives three arguments: connection, event, and data.

ruby
Tina4::Router.websocket "/ws/echo" do |connection, event, data|  if event == :message    connection.send("Echo: #{data}")  endend

Queues#

The file-based backend works out of the box. No Redis. No RabbitMQ.

ruby
queue = Tina4::Queue.new(topic: "emails")queue.push({  to: "alice@example.com",  subject: "Welcome",  body: "Your account is ready."})

WSDL#

ruby
class Calculator < Tina4::WSDL  service_url "http://localhost:7147/calculator"  def add(a, b)    { result: a + b }  endend

GraphQL#

Tina4 includes a built-in GraphQL engine. No external gems.

ruby
schema = Tina4::GraphQLSchema.newgql = Tina4::GraphQL.new(schema)gql.register_route  # POST /graphql, GET /graphql (playground)

POST queries to /graphql, or visit it in a browser for the GraphiQL IDE.

graphql
{ users(limit: 5) { id name email } }

Full details on manual schema definition, mutations, variables, fragments, and programmatic usage.

Localization (i18n)#

Set TINA4_LOCALE in .env to change the framework language.

ruby
puts Tina4.t("server_stopped")  # "Server stopped." (en)

Back to top

HTML Builder#

ruby
el = Tina4::HtmlElement.new("div", { class: "card" }, ["Hello"])el.to_s  # => '<div class="card">Hello</div>'# Nesting with callcard = Tina4::HtmlElement.new("div").call(  { class: "card" },  Tina4::HtmlElement.new("h2").call("Title"),  Tina4::HtmlElement.new("p").call("Content"),)# Helper methodsinclude Tina4::HtmlHelpershtml = _div({ class: "card" }, _h1("Title"), _p("Description"))

Events#

ruby
# Subscribe to an eventTina4::Events.on("user.created") do |payload|  puts "New user: #{payload[:name]}"end# Subscribe once - auto-removes after first fireTina4::Events.once("app.boot") do |payload|  puts "App booted at #{payload[:time]}"end# Emit an event anywhere in the appTina4::Events.emit("user.created", { name: "Alice", id: 42 })

Events are synchronous by default. Emit inside a service to run them off the request thread.

Logging#

ruby
Tina4::Log.info("Server ready on port 7147")Tina4::Log.debug("Params: #{request.params.inspect}")Tina4::Log.warning("Deprecated method called")Tina4::Log.error("Database connection failed")

Set TINA4_LOG_LEVEL=ALL in .env to see every level. Production default is INFO. Log output goes to stdout and to logs/tina4.log.

Response Cache#

ruby
# Cache for 60 seconds (default)Tina4::Router.get("/api/products") do |request, response|  response.json({ products: Product.all })end.cache# Custom TTL in secondsTina4::Router.get("/api/summary") do |request, response|  response.json({ total: Order.count })end.cache(300)# Cache is keyed on method + path + query string.# Clear all cached responses:Tina4::Cache.flush

Health Endpoint#

Tina4 registers /health automatically. No setup needed.

bash
curl http://localhost:7147/health
json
{ "status": "ok", "uptime": 142, "version": "3.10.20" }

The response includes framework version, uptime in seconds, and database connectivity when a TINA4_DATABASE_URL is configured. Use this endpoint for container liveness and readiness probes.

DI Container#

ruby
# Register a service by nameTina4::Container.register(:mailer) { Mailer.new(ENV["TINA4_MAIL_HOST"]) }# Register a singleton (resolved once, reused everywhere)Tina4::Container.register(:config, singleton: true) { AppConfig.load }# Resolve anywhere in the appmailer = Tina4::Container.resolve(:mailer)mailer.send_welcome(user)

Registrations are lazy: the block runs on first .resolve. Singletons cache the result for the lifetime of the process.

Error Overlay#

When TINA4_DEBUG=true, unhandled exceptions render an in-browser overlay.

TINA4_DEBUG=true

The overlay shows the exception class, message, and a syntax-highlighted stack trace with source lines. It replaces the default HTML error page only in debug mode. In production the framework returns a plain 500 response and writes the full trace to the log.

Dev Admin#

http://localhost:7147/__dev

The /__dev dashboard is available when TINA4_DEBUG=true. It lists every registered route (method, path, middleware, auth), active services, queue depths, and recent log lines. No configuration needed: visit the URL after tina4 serve starts.

CLI Commands#

bash
tina4 init ruby my-project   # Scaffold a new Ruby projecttina4 serve                  # Start dev server (port 7147, live reload)tina4 serve --port 8080      # Custom porttina4 migrate                # Run pending SQL migrationstina4 migrate --create name  # Generate a timestamped migration filetina4 test                   # Run the RSpec test suitetina4 build                  # Compile SCSS, bundle assets for productiontina4 routes                 # Print all registered routes to stdout

MCP Server#

Tina4 starts an MCP (Model Context Protocol) server automatically when TINA4_DEBUG=true.

http://localhost:7147/__mcp
ruby
# Register a custom MCP toolTina4::MCP.register_tool("get_user") do |params|  user = User.find(params["id"].to_i)  { id: user.id, name: user.name }end

AI agents connect to /__mcp and discover routes, ORM models, and registered tools. Disable with TINA4_MCP=false in .env.

FakeData#

ruby
fake = Tina4::FakeData.newputs fake.name          # "Liam Torres"puts fake.email         # "liam.torres@example.com"puts fake.phone         # "+1-555-0147"puts fake.address       # "12 Elm Street, Springfield"puts fake.company       # "Bright Horizon Ltd"puts fake.paragraph     # Lorem-style filler textputs fake.integer(1, 100) # Random integer between 1 and 100puts fake.uuid          # "a3f1c2d4-..."# Seed for reproducible datafake = Tina4::FakeData.new(seed: 42)

Use FakeData in tests and migrations to generate consistent fixture data without external gems.


📕 Download the book#

Tina4 for Ruby Developers (PDF): full reference, printable, with clickable table of contents and PDF outline. Regenerated with every release.