Database
For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Queries

Application data with D1 and Drizzle: customer ownership, reads and writes, relationships, pagination, and query design for new features.

The database is where your product keeps structured information: saved documents, projects, preferences, and the relationships between them. Edge Kit connects Drizzle to Cloudflare D1 so your application can work with that data through typed queries.

Your table definitions supply the available fields and their types. The existing client provides the connection; a feature adds its queries and the access rules around them.

Data access

Database operations run on the Worker through the DB binding. The browser asks a server function for a result instead of connecting directly to D1.

Keep the responsibilities clear:

ConcernResponsibility
Input validationAccepted fields, formats, and bounds
AuthenticationThe customer making the request
AuthorizationThe records and actions that customer can access
QueryThe data to read or change
ResponseThe fields the interface needs

The protected calls guide combines these checks. Reuse the same boundary for reads, updates, and deletion.

Customer ownership

Most product records belong to a customer or another resource. Model that relationship explicitly in the schema, then include it in the operation that accesses the record.

For example, a request for a saved note contains the note identifier. The server derives the customer identity from the session and requires the note to belong to that customer. Accepting an owner identifier from the browser would let the caller choose whose records to query.

Record access

A valid session proves who is making the request. The query still needs to establish that the requested record belongs to that customer. Apply the ownership condition to updates and deletes as well as reads.

The new notes table in schema changes demonstrates a customer relationship. For a feature with nested resources, such as attachments belonging to a document, follow the relationship back to the customer before granting access.

Reads

Select the fields needed for the current view. A list often needs an identifier, label, and a few summary values; a detail page can load the remaining fields separately.

This example uses the notes table from the schema guide. userId represents the identity already resolved by the server:

import { eq } from "drizzle-orm";

import { db } from "@/db";
import { note } from "@/db/schema";

const notes = await db
  .select({ id: note.id, title: note.title })
  .from(note)
  .where(eq(note.userId, userId))
  .limit(25);

Keep conditions in the database query instead of loading every row and filtering in the browser. That reduces the amount of data returned and gives the access rule a single place to be reviewed.

For a detail request, decide how the interface should handle a missing or inaccessible record. A not-found state can avoid revealing whether another customer's record exists. The response should give the UI enough information to recover without returning private database details.

Writes

Validate submitted values before a write. Use the session for ownership fields, and construct the values you intend to store rather than copying an arbitrary request object into the database.

Creation, updates, and deletion have different responsibilities:

  • Creation establishes ownership and any required relationship to a parent resource.
  • Updates restrict both the target record and the fields the customer may change.
  • Deletion considers related records, stored files, and any background work that refers to the resource.

For example, a rename can require both the requested note and its owner to match. Here, input has already been validated and userId comes from the server session:

import { and } from "drizzle-orm";

const [updatedNote] = await db
  .update(note)
  .set({ title: input.title })
  .where(and(eq(note.id, input.id), eq(note.userId, userId)))
  .returning({ id: note.id });

The operation cannot rename another customer's note by changing its identifier. If no row matches, handle the missing result before reporting a successful save.

After a successful write, refresh the corresponding client query so the interface shows the saved state. Data fetching covers this part of the workflow.

Relationships

Foreign keys describe how records depend on one another. They also make deletion policy explicit. Some child records should disappear with their parent; others may need to remain for the product's history.

Choose that policy for each relationship rather than applying cascading deletion everywhere. If a record also owns a file in R2, deleting its database row does not by itself remove the object. Connect database and file cleanup in the feature's lifecycle.

Keep business rules in the server operation even when the schema also enforces a constraint. The constraint protects data integrity; the operation can return a useful explanation to the customer.

Ordering and pagination

A growing list should request a bounded page of data. Validate page size and filters on the server, and use an explicit order so successive requests produce a predictable result.

For chronological lists, a timestamp with a stable tie-breaker is more reliable than assuming identifiers describe creation order. Cursor pagination can suit frequently changing feeds; page-based navigation can be simpler for smaller administrative lists. Choose the approach around the interface your customers need.

Include the same filters and pagination values in the client query key. Otherwise two different views may reuse the same cached result.

Query performance

Design indexes around frequent filters and ordering, such as a customer's resource list. An index has a storage and write cost, so add it for a query you actually need rather than for every column.

Inspect the local database through Local Explorer, test with representative data, and review slow operations in your logs. Use D1 guidance for service-specific behavior and limits.

Schema changes still go through reviewed migrations. Query code and the deployed schema must remain compatible during a release.

How is this guide?

Last updated on

On this page

Ship globally on the edge. In minutes.Try Edge Kit