Skip to main content
This quickstart guide will walk you through creating a complete data pipeline: from defining a datasource to querying data with full type safety.

Prerequisites

Before starting, make sure you have:
  • Node.js 20 LTS or later installed
  • Completed the installation steps
  • Run npx tinybird init to initialize your project

Step 1: Define a Datasource

Open src/tinybird/datasources.ts and define your first datasource. We’ll create a datasource to track page views:
src/tinybird/datasources.ts
import { defineDatasource, t, engine, type InferRow } from "@tinybirdco/sdk";

export const pageViews = defineDatasource("page_views", {
  description: "Page view tracking data",
  schema: {
    timestamp: t.dateTime(),
    pathname: t.string(),
    session_id: t.string(),
    country: t.string().lowCardinality().nullable(),
  },
  engine: engine.mergeTree({
    sortingKey: ["pathname", "timestamp"],
  }),
});

// Export row type for ingestion
export type PageViewsRow = InferRow<typeof pageViews>;
The InferRow type utility extracts the TypeScript type from your schema definition, giving you type-safe ingestion.

Understanding the Schema

  • t.dateTime() - Timestamp column for time-series data
  • t.string() - String columns for text data
  • .lowCardinality() - Optimization for columns with few unique values
  • .nullable() - Makes the field optional

Step 2: Define an Endpoint

Now create a query endpoint in src/tinybird/pipes.ts:
src/tinybird/pipes.ts
import { defineEndpoint, node, t, p, type InferParams, type InferOutputRow } from "@tinybirdco/sdk";

export const topPages = defineEndpoint("top_pages", {
  description: "Get the most visited pages",
  params: {
    start_date: p.dateTime(),
    end_date: p.dateTime(),
    limit: p.int32().optional(10),
  },
  nodes: [
    node({
      name: "aggregated",
      sql: `
        SELECT pathname, count() AS views
        FROM page_views
        WHERE timestamp >= {{DateTime(start_date)}}
          AND timestamp <= {{DateTime(end_date)}}
        GROUP BY pathname
        ORDER BY views DESC
        LIMIT {{Int32(limit, 10)}}
      `,
    }),
  ],
  output: {
    pathname: t.string(),
    views: t.uint64(),
  },
});

// Export endpoint types
export type TopPagesParams = InferParams<typeof topPages>;
export type TopPagesOutput = InferOutputRow<typeof topPages>;

Understanding Parameters

  • p.dateTime() - Required datetime parameter
  • p.int32().optional(10) - Optional parameter with default value of 10
  • {{DateTime(start_date)}} - Parameter placeholder in SQL
  • {{Int32(limit, 10)}} - Parameter with inline default

Step 3: Create Your Client

Update src/tinybird/client.ts to export your typed client:
src/tinybird/client.ts
import { Tinybird } from "@tinybirdco/sdk";
import { pageViews, type PageViewsRow } from "./datasources";
import { topPages, type TopPagesParams, type TopPagesOutput } from "./pipes";

export const tinybird = new Tinybird({
  datasources: { pageViews },
  pipes: { topPages },
});

// Re-export types for convenience
export type { PageViewsRow, TopPagesParams, TopPagesOutput };
export { pageViews, topPages };
The Tinybird class automatically infers all types from your datasource and pipe definitions, providing full autocomplete in your IDE.

Step 4: Configure Path Alias (Optional)

For easier imports, add a path alias to your tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "@tinybird/client": ["./src/tinybird/client.ts"]
    }
  }
}
This lets you import with:
import { tinybird } from "@tinybird/client";
Instead of:
import { tinybird } from "../../src/tinybird/client";

Step 5: Start Development Mode

Run the dev command to watch your schema files and automatically sync changes to Tinybird:
npx tinybird dev
You should see output like:
✓ Connected to Tinybird workspace
✓ Synced datasource: page_views
✓ Synced endpoint: top_pages
👀 Watching for changes...
Dev mode only works on feature branches (not main) to prevent accidental production changes. Use tinybird deploy for production deployments.

Step 6: Ingest Data

Now use your typed client to ingest data. Create a new file or use an API route:
import { tinybird, type PageViewsRow } from "@tinybird/client";

// Type-safe data ingestion
await tinybird.pageViews.ingest({
  timestamp: "2024-01-15 10:30:00",
  pathname: "/home",
  session_id: "abc123",
  country: "US",
});

// Your IDE will provide autocomplete for all fields
// TypeScript will error if you pass invalid data types

Batch Ingestion

For better performance, ingest multiple rows at once:
import { tinybird } from "@tinybird/client";

const events = [
  {
    timestamp: "2024-01-15 10:30:00",
    pathname: "/home",
    session_id: "session_1",
    country: "US",
  },
  {
    timestamp: "2024-01-15 10:31:00",
    pathname: "/pricing",
    session_id: "session_2",
    country: "GB",
  },
];

await tinybird.pageViews.ingest(events);

Step 7: Query Data

Query your endpoint with full type safety:
import { tinybird, type TopPagesOutput } from "@tinybird/client";

// Type-safe queries with autocomplete
const result = await tinybird.topPages.query({
  start_date: "2024-01-01 00:00:00",
  end_date: "2024-01-31 23:59:59",
  limit: 5,
});

// result.data is fully typed: { pathname: string, views: bigint }[]
result.data.forEach((row: TopPagesOutput) => {
  console.log(`${row.pathname}: ${row.views} views`);
});
Your IDE will provide autocomplete for query parameters and result fields. TypeScript catches type errors at compile time!

Step 8: Additional Datasource Operations

The SDK provides several methods for managing datasource data:
import { tinybird } from "@tinybird/client";

// Append rows from a remote file
await tinybird.pageViews.append({
  url: "https://example.com/page_views.csv",
});

// Replace all rows from a remote file
await tinybird.pageViews.replace({
  url: "https://example.com/page_views_full_snapshot.csv",
});

// Delete matching rows
await tinybird.pageViews.delete({
  deleteCondition: "country = 'XX'",
});

// Preview matching rows without deleting (dry run)
await tinybird.pageViews.delete({
  deleteCondition: "country = 'XX'",
  dryRun: true,
});

// Remove all rows from the datasource
await tinybird.pageViews.truncate();
Be careful with delete and truncate operations - they permanently remove data. Always test with dryRun: true first.

Next.js Integration

For Next.js projects, run Tinybird dev alongside your Next.js dev server:

Install Concurrently

npm install --save-dev concurrently

Update package.json

package.json
{
  "scripts": {
    "dev": "concurrently -n next,tinybird \"next dev\" \"tinybird dev\"",
    "tinybird:build": "tinybird build"
  }
}
Now npm run dev starts both servers together!
The CLI automatically loads .env.local and .env files, so no additional setup is needed.

What You’ve Built

Congratulations! You’ve created a complete typed data pipeline:
1

Defined a Datasource

Created a typed schema for page view tracking
2

Created an Endpoint

Built a query to get the most visited pages
3

Generated a Client

Got a fully-typed client with autocomplete
4

Ingested Data

Added data with type-safe ingestion
5

Queried Data

Retrieved results with full type inference

Next Steps

Now that you have a working pipeline, explore more advanced features:

Schema Types

Learn about all available schema types and modifiers

Endpoints

Create advanced query endpoints with complex SQL

Connections

Connect to Kafka, S3, and other data sources

CLI Commands

Master the CLI for development and deployment

Build docs developers (and LLMs) love