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:
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:
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.
For easier imports, add a path alias to your 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:
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
{
"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:
Defined a Datasource
Created a typed schema for page view tracking
Created an Endpoint
Built a query to get the most visited pages
Generated a Client
Got a fully-typed client with autocomplete
Ingested Data
Added data with type-safe ingestion
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