Astro + Turso: The Perfect Pair for Building Fast, Scalable Websites

Astro + Turso: The Perfect Pair for Building Fast, Scalable Websites

Introduction

In this article, we’ll show you how to use Astro and Turso together to build fast, scalable content-driven websites.

When building a modern website, developers often prioritize speed, ease of content management, and a seamless user experience. Astro and Turso are two tools that cater to these needs effectively, providing robust frameworks for developing content-driven websites that are both fast and scalable.

Astro

Astro is an open-source web framework designed specifically for building content-heavy websites such as blogs, e-commerce sites, and marketing pages. It follows a unique server-first rendering model that minimizes client-side JavaScript, which makes it highly performant by default. This is achieved through its island architecture, where only specific components are hydrated and rendered on the client-side, reducing overall page load times and improving SEO performance. Additionally, Astro is framework-agnostic, allowing developers to use components from popular JavaScript frameworks like React, Vue, Svelte, and others, making it flexible and easy to integrate into existing projects.

Astro also supports both Static Site Generation (SSG) and Server-Side Rendering (SSR), giving developers control over how content is generated and delivered. This makes Astro a versatile option for projects that range from static blogs to more dynamic web applications that need real-time updates. For more details on Astro’s capabilities, you can check out their official documentation.

Turso

Turso is a distributed database platform built on SQLite, known for its lightweight footprint and efficiency. Turso extends SQLite’s capabilities by supporting distributed data storage and retrieval, which makes it ideal for managing content across multiple regions with low-latency access. This setup is particularly useful for modern web applications that need to deliver content quickly and efficiently to users around the world.

By leveraging SQLite, Turso maintains compatibility with an array of existing tools and workflows, while its edge-oriented architecture ensures fast data access and real-time updates. This enables Astro to efficiently handle dynamic content, supporting real-time data fetching and server-side rendering without sacrificing performance. For more information on Turso’s SQLite integration and its benefits, check out the Turso documentation and additional insights on distributed data handling.

Turso can handle dynamic data efficiently and support Astro’s SSR capabilities to deliver up-to-date content without compromising speed. This combination makes Astro and Turso a powerful duo for developers looking to create fast, responsive, and scalable content-driven websites.

Initial Setup

Building the Website with Astro

Let’s start by creating a new Astro project.

Copy
npm create astro@latest

Follow the prompts to set up your project. Once the project is created, navigate into the project directory and install the dependencies.

Copy
cd your-project-name npm install

Note that we are using the template with sample files. We will not be building front-end components in this tutorial. You should see the following directory structure (skipping files that are not relevant to this tutorial).

Copy
your-project-name/ ├── node_modules/ ├── public/ ├── src/ ├── components/ └── Card.astro ├── layouts/ └── Layout.astro └── pages/ └── └── index.astro

Creating a Database and Table with Turso CLI

0. Install Turso CLI and Login

Install Turso CLI: If you haven’t already, install the Turso CLI on your system. Instructions for installation are available on Turso’s documentation.

Then login to your Turso account:

Copy
turso auth login

1. Create a New Database

To create a new database, run:

Copy
turso db create my-database

Turso will create the database in the nearest location based on your IP.

2. Access the Database Shell

After creating your database, access it using:

Copy
turso db shell my-database

3. Create the posts Table

Inside the Turso database shell, create a table called posts:

Copy
CREATE TABLE posts ( slug TEXT PRIMARY KEY, title TEXT NOT NULL, body TEXT NOT NULL );

This creates a table with slug as the primary key and both title and body columns as required fields.

Verify that the table was created successfully and add dummy data:

Copy
SELECT * FROM posts; INSERT INTO posts (slug, title, body) VALUES ('my-first-post', 'My First Post', 'This is the first post.'), ('my-second-post', 'My Second Post', 'Is the 2nd one better?'), ('my-third-post', 'My Third Post', 'Practice makes perfect.');

Then exit the db shell:

Copy
.quit

4. Retrieve the Database URL

To retrieve the database URL, run:

Copy
turso db show my-database --url

Store it a the .env file at the root of the project.

Copy
├── node_modules/ ├── public/ ├── src/ ├── components/ └── Card.astro ├── layouts/ └── Layout.astro └── pages/ └── index.astro └── .env
Copy
# .env TURSO_URL="libsql://something.turso.io"

5. Generate a Read/Write Authentication Token

To allow external applications to access your database, generate a read/write token:

Copy
turso db tokens create my-database

This command will output a token that you can use to connect your application to the database securely. Add it to the .env file at the root of the project.

Copy
# .env TURSO_URL="libsql://my-database-username.turso.io" TURSO_AUTH=some.long.string

Integrating Turso with Astro

Interacting with the Database

We will now create a new file src/lib/db.ts to interact with the Turso database. But first we need to install @libsql/client to interact with the database and dotenv to load the environment variables from our .env file.

Copy
npm install @libsql/client dotenv
Copy
// src/lib/db.ts import { createClient } from '@libsql/client'; // Load environment variables from .env file for local development import dotenv from 'dotenv'; dotenv.config(); export interface Post { slug: string; title: string; body: string; } async function queryDatabase(client: { execute: (arg0: string) => any; }, query: string) { try { const result = await client.execute(query); return result; } catch (error) { console.error('Error querying the database:', error); } } async function getClient() { const tursoUrl = process.env.TURSO_URL; if (!tursoUrl) { throw new Error('TURSO_URL is not defined in the environment variables'); } const tursoAuth = process.env.TURSO_AUTH; if (!tursoAuth) { throw new Error('TURSO_AUTH is not defined in the environment variables'); } return createClient({ url: tursoUrl, authToken: tursoAuth, }); } function mapResultToObjects(result: any) { const { columns, rows } = result; return rows.map((row: any) => { const obj: any = {}; columns.forEach((col: string) => { obj[col] = row[col]; }); return obj; }); } export async function getAllPosts():Promise<Post[]> { const client = await getClient(); const data = await queryDatabase(client, 'SELECT * FROM posts'); if (data) { const posts: Post[] = mapResultToObjects(data); return posts; } console.error('No posts found'); return []; }

Fetching Data in Astro

Now we will fetch the posts from the Turso database and display them on our index page and create a new page to display the content of a single post.

Index Page

In src/pages/index.astro, we will add the following code to fetch the posts from the Turso database and display them on the page.

Copy
--- // src/pages/index.astro import Card from '../components/Card.astro'; // Fetch posts from the Turso database import { getAllPosts } from '../lib/db'; const posts = await getAllPosts(); --- <div> <h1>My Blog</h1> <div class="grid"> {posts.map((post:Post) => ( <Card href={`/posts/${post.slug}`} title={post.title} body={post.body} /> ))} </div> </div>

Post Page

We will create a new file src/pages/[slug].astro to display the content of a single post.

Copy
--- // src/pages/[slug].astro import { getAllPosts, type Post } from '../../lib/utils/db'; export async function getStaticPaths() { const posts = await getAllPosts(); return posts.map((post: Post) => ({ params: { slug: post.slug }, props: post, })); } export async function get({ props }: { props: any }) { return { props, }; } --- <div> <h1>{Astro.props.title}</h1> <p>{Astro.props.body}</p> </div>

And Voila!, we have a fully functional blog site that fetches its content from a Turso database.

Note that in the current setup we are using Static Site Generation (SSG) to fetch the posts at build time. Depending on the use case, we could go two ways:

  • Trigger a new static build when a post is created or updated, using a webhook for example.
  • Use a serverless function to fetch the posts on each request.

We could also fetch the posts at build time but use SSR to fetch data from a comments table on each request for example.

Further Developments

A natural next step would be to add a comments table to the database and use SSR to fetch the comments for a given post on each request. We could then use client side JavaScript to add comments to the posts.

If traditional database tools are not practical enough, we could also develop an admin panel to manage the content of the blog. We could use again some client side JavaScript to add/update/delete content from the database.

We could also connect other services to the Turso database, like a python script to write new posts to the database.

Featured Posts