September 14, 2020·4 min read·engineeringsqlitedatasetteapi

Datasette exposes saved writes through JSON

Datasette 0.49 gives writable canned queries a JSON request and response interface. Custom path parameters also let page templates serve related URLs.

A saved SQL write can be useful to both a browser form and a program. Requiring the program to imitate the form's redirect behavior adds work unrelated to the database operation. Datasette 0.49 adds a JSON interface to writable canned queries, allowing callers to submit parameters and request a structured result. The September 14 release announcement also adds custom path parameters for page templates, extending the application's ability to serve purpose-built pages around SQLite data.

The JSON change retains the saved query as the definition of the write. A caller supplies values to a configured operation, and the response represents its outcome. This differs from creating an unrestricted endpoint where a client sends arbitrary write SQL. The operation's definition remains something the publisher controls in Datasette configuration.

I value this scale of application development around SQLite. A small write operation can remain a visible SQL statement while serving more than one kind of client. The useful abstraction is the named operation and its parameters, which are easier to inspect than a form submission treated as an undocumented API.

Request encoding and response format are separate choices

The 0.49 query documentation describes posting a JSON document containing key/value parameters to the writable query's existing path. Form encoding continues to work. Adopting a programmatic client therefore does not require replacing the form interface or introducing a second SQL definition for the same operation.

The caller can request a JSON response in several ways. An Accept: application/json header is one option. A _json=1 query-string parameter is another. The documentation also permits _json in the submitted JSON or form body. These choices concern the response representation, separately from whether the request body uses JSON or form encoding.

For an existing writable query named add_message, a programmatic request can use the documented JSON shape. This illustrative request assumes the caller already has whatever authorization the query requires.

POST /mydatabase/add_message HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json

{"message": "Message goes here"}

The body provides the message parameter. The content type identifies its encoding, while Accept requests the structured response. Merely sending JSON should not be confused with explicitly choosing the response form. The documentation treats these as related capabilities with distinct controls.

That separation matters for an incremental application. A form can continue posting ordinary encoded fields, and a script can post JSON to the same configured operation. Either can request a JSON outcome when that is useful. The publisher does not need to maintain separate SQL statements solely because the two clients package their parameters differently.

The response preserves configured outcomes

The documented JSON response contains ok, message and redirect. The message and redirect respect the canned query's configured success and error properties. This carries the existing operation's response settings into a representation a client can consume directly. A client can read the intended destination without depending on browser navigation to discover it.

The response does not turn every write into a table of returned rows. Its documented purpose is to represent execution success and the associated message and redirect. A client should use that specified outcome shape instead of assuming that any JSON database API necessarily returns a query result set. The operation remains a writable canned query with its configured response behavior.

Canned queries are read-only by default. The existing write: true property identifies a writable query, and the allow configuration describes permission checks. Version 0.49 adds another access format for such queries. It does not remove the need to distinguish the SQL the publisher has approved from the users permitted to execute it.

The parameter mechanism also remains useful in this form. Ordinary values can come from the request, while magic parameters can supply actor fields and the current time. A JSON client need not become the authority for a server-generated timestamp merely because it now uses a structured request. The configured SQL still records where those values enter the operation.

The custom-template documentation at the same tag describes filenames containing variables in braces. A template at templates/pages/about/{slug}.html can handle requests such as /about/news. Datasette supplies a slug variable with the value news for that request.

This changes how many application pages one template can describe. A static page filename identifies a particular page, while a path parameter allows a template to receive part of the requested resource name. The template then needs to interpret that value according to the application's data and content rules. Matching a path does not establish that the requested content exists.

The release consequently includes raise_404(), which allows a template to report missing content. The documentation says calling it ignores the template's other content and displays the default not-found page. This is a specific response mechanism for the case where routing succeeded but the referenced resource could not be found.

I prefer that explicit missing-resource behavior to treating every matching path as a successful page. A parameterized template expands the set of paths the application can recognize, so it also needs a way to reject names that do not identify anything. The route variable supplies an input, and the template still decides whether there is content to render.

The interfaces can share an operation without sharing navigation

The two additions support different parts of a small data application. Parameterized pages can present related resources through one template, while JSON callers can invoke an approved write without depending on a form redirect. Neither requires changing the stored SQLite rows merely to accommodate a new URL or request encoding.

The resulting contract remains specific. A configured query defines the SQL and its parameters, its permission settings control invocation, and its response settings supply messages and destinations. The JSON API exposes that outcome as data. This allows a client to use the write deliberately while the browser interface continues to use the same saved operation.