<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: webapis</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/webapis.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2020-04-16T22:44:55+00:00</updated><author><name>Simon Willison</name></author><entry><title>SQL is a better API language than GraphQL – Convince me otherwise</title><link href="https://simonwillison.net/2020/Apr/16/sql-is-a-better-api-language-than-graphql/#atom-tag" rel="alternate"/><published>2020-04-16T22:44:55+00:00</published><updated>2020-04-16T22:44:55+00:00</updated><id>https://simonwillison.net/2020/Apr/16/sql-is-a-better-api-language-than-graphql/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://news.ycombinator.com/item?id=22892946"&gt;SQL is a better API language than GraphQL – Convince me otherwise&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A &lt;a href="https://twitter.com/simonw/status/1250803209871847426"&gt;flippant tweet&lt;/a&gt; I posted this morning blew up today and ended up on the Hacker News homepage.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1250803209871847426"&gt;My thread on Twitter&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/hacker-news"&gt;hacker-news&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/graphql"&gt;graphql&lt;/a&gt;&lt;/p&gt;



</summary><category term="hacker-news"/><category term="sql"/><category term="webapis"/><category term="graphql"/></entry><entry><title>paginate-json</title><link href="https://simonwillison.net/2019/Jun/12/paginate-json/#atom-tag" rel="alternate"/><published>2019-06-12T15:22:20+00:00</published><updated>2019-06-12T15:22:20+00:00</updated><id>https://simonwillison.net/2019/Jun/12/paginate-json/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://pypi.org/project/paginate-json/"&gt;paginate-json&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I released a fun tiny utility: paginate-json, which knows how to paginate through JSON APIs that use the HTTP Link header for pagination. I built it so I could pull data from the GitHub API and pipe it directly into SQLite via sqlite-utils.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1138828809849847808"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/json"&gt;json&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/p&gt;



</summary><category term="json"/><category term="projects"/><category term="webapis"/><category term="sqlite-utils"/></entry><entry><title>Develop Your Naturalist Superpowers with Observable Notebooks and iNaturalist</title><link href="https://simonwillison.net/2018/Dec/18/develop-your-naturalist-superpowers/#atom-tag" rel="alternate"/><published>2018-12-18T22:39:19+00:00</published><updated>2018-12-18T22:39:19+00:00</updated><id>https://simonwillison.net/2018/Dec/18/develop-your-naturalist-superpowers/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://24ways.org/2018/observable-notebooks-and-inaturalist/"&gt;Develop Your Naturalist Superpowers with Observable Notebooks and iNaturalist&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Natalie’s article for this year’s 24 ways advent calendar shows how you can use Observable notebooks to quickly build interactive visualizations against web APIs. She uses the iNaturalist API to show species of Nudibranchs that you might see in a given month, plus a Vega-powered graph of sightings over the course of the year. This really inspired me to think harder about how I can use Observable to solve some of my API debugging needs, and I’ve already spun up a couple of private Notebooks to exercise new APIs that I’m building at work. It’s a huge productivity boost.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/Natbat/status/1074820561509859328"&gt;@natbat&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/natalie-downe"&gt;natalie-downe&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/inaturalist"&gt;inaturalist&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable"&gt;observable&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nudibranchs"&gt;nudibranchs&lt;/a&gt;&lt;/p&gt;



</summary><category term="natalie-downe"/><category term="webapis"/><category term="inaturalist"/><category term="observable"/><category term="nudibranchs"/></entry><entry><title>Datasette: instantly create and publish an API for your SQLite databases</title><link href="https://simonwillison.net/2017/Nov/13/datasette/#atom-tag" rel="alternate"/><published>2017-11-13T23:49:28+00:00</published><updated>2017-11-13T23:49:28+00:00</updated><id>https://simonwillison.net/2017/Nov/13/datasette/#atom-tag</id><summary type="html">
    &lt;p&gt;I just shipped the first public version of &lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;, a new tool for creating and publishing JSON APIs for SQLite databases.&lt;/p&gt;
&lt;p&gt;You can try out out right now at &lt;a href="https://fivethirtyeight.datasettes.com/"&gt;fivethirtyeight.datasettes.com&lt;/a&gt;, where you can explore SQLite databases I built from Creative Commons licensed CSV files &lt;a href="https://github.com/fivethirtyeight/data"&gt;published by FiveThirtyEight&lt;/a&gt;. Or you can check out &lt;a href="https://parlgov.datasettes.com/"&gt;parlgov.datasettes.com&lt;/a&gt;, derived from the &lt;a href="http://www.parlgov.org/"&gt;parlgov.org&lt;/a&gt; database of world political parties which illustrates some advanced features such as &lt;a href="https://parlgov.datasettes.com/parlgov-25f9855/view_party"&gt;SQLite views&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fivethirtyeight.datasettes.com/fivethirtyeight/most-common-name%2Fsurnames"&gt;&lt;img alt="Common surnames from fivethirtyeight" src="https://static.simonwillison.net/static/2017/fivethirtyeight-surnames.png"  style="width: 100%" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or you can try it out on your own machine. If you run OS X and use Google Chrome, try running the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install datasette
datasette ~/Library/Application\ Support/Google/Chrome/Default/History
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will start a web server on &lt;a href="http://127.0.0.1:8001/"&gt;http://127.0.0.1:8001/&lt;/a&gt; displaying an interface that will let you browse your Chrome browser history, which is conveniently stored in a SQLite database.&lt;/p&gt;
&lt;p&gt;Got a SQLite database you want to share with the world? Provided you have &lt;a href="https://zeit.co/now"&gt;Zeit Now&lt;/a&gt; set up on your machine, you can publish one or more databases with a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette publish now my-database.db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command will whir away for about a minute and then spit out a URL to a hosted version of datasette with your database (or databases) ready to go. This is how I’m hosting the fivethirtyeight and parlgov example datasets, albeit on a custom domain behind a &lt;a href="https://cloudflare.com/"&gt;Cloudflare&lt;/a&gt; cache.&lt;/p&gt;
&lt;h2&gt;&lt;a id="The_datasette_API_19"&gt;&lt;/a&gt;The datasette API&lt;/h2&gt;
&lt;p&gt;Everything datasette can do is driven by URLs. Queries can produce responsive HTML pages (I’m using a variant of &lt;a href="https://css-tricks.com/responsive-data-tables/"&gt;this responsive tables pattern&lt;/a&gt; for smaller screens) or with the &lt;code&gt;.json&lt;/code&gt; or &lt;code&gt;.jsono&lt;/code&gt; extension can produce JSON. All JSON responses are served with an &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; HTTP header, meaning you can query them from any page.&lt;/p&gt;
&lt;p&gt;You can try that right now in your browser’s developer console. Navigate to &lt;a href="http://www.example.com/"&gt;http://www.example.com/&lt;/a&gt; and enter the following in the console:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fetch(
    &amp;quot;https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9/avengers%2Favengers.jsono&amp;quot;
).then(
    r =&amp;gt; r.json()
).then(data =&amp;gt; console.log(
    JSON.stringify(data.rows[0], null, '  ')
))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll see the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &amp;quot;rowid&amp;quot;: 1,
  &amp;quot;URL&amp;quot;: &amp;quot;http://marvel.wikia.com/Henry_Pym_(Earth-616)&amp;quot;,
  &amp;quot;Name/Alias&amp;quot;: &amp;quot;Henry Jonathan \&amp;quot;Hank\&amp;quot; Pym&amp;quot;,
  &amp;quot;Appearances&amp;quot;: 1269,
  &amp;quot;Gender&amp;quot;: &amp;quot;MALE&amp;quot;,
  &amp;quot;Full/Reserve Avengers Intro&amp;quot;: &amp;quot;Sep-63&amp;quot;,
  &amp;quot;Year&amp;quot;: 1963,
  &amp;quot;Years since joining&amp;quot;: 52,
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the API sits behind Cloudflare with a year-long cache expiry header, responses to any query like this should be lightning-fast.&lt;/p&gt;
&lt;p&gt;Datasette supports a limited form of filtering based on URL parameters, inspired by Django’s ORM. Here’s an example: by appending &lt;code&gt;?CLOUDS=1&amp;amp;MOUNTAINS=1&amp;amp;BUSHES=1&lt;/code&gt; to the FiveThirtyEight dataset of episodes of &lt;a href="https://en.wikipedia.org/wiki/The_Joy_of_Painting"&gt;Bob Ross’ The Joy of Painting&lt;/a&gt; we can see every episode in which Bob paints clouds, bushes AND mountains:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9/bob-ross%2Felements-by-episode?CLOUDS=1&amp;amp;MOUNTAINS=1&amp;amp;BUSHES=1"&gt;https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9/bob-ross%2Felements-by-episode?CLOUDS=1&amp;amp;MOUNTAINS=1&amp;amp;BUSHES=1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And here’s &lt;a href="https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9/bob-ross%2Felements-by-episode.jsono?CLOUDS=1&amp;amp;MOUNTAINS=1&amp;amp;BUSHES=1"&gt;the same episode list as JSON&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;a id="Arbitrary_SQL_55"&gt;&lt;/a&gt;Arbitrary SQL&lt;/h2&gt;
&lt;p&gt;The most exciting feature of datasette is that it allows users to execute &lt;em&gt;arbitrary SQL queries&lt;/em&gt; against the database. Here’s &lt;a href="https://fivethirtyeight.datasettes.com/fivethirtyeight?sql=select+%28select+sum%28%22APPLE_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+APPLE_FRAME%2C%0D%0A%28select+sum%28%22AURORA_BOREALIS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+AURORA_BOREALIS%2C%0D%0A%28select+sum%28%22BARN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BARN%2C%0D%0A%28select+sum%28%22BEACH%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BEACH%2C%0D%0A%28select+sum%28%22BOAT%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BOAT%2C%0D%0A%28select+sum%28%22BRIDGE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BRIDGE%2C%0D%0A%28select+sum%28%22BUILDING%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BUILDING%2C%0D%0A%28select+sum%28%22BUSHES%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+BUSHES%2C%0D%0A%28select+sum%28%22CABIN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CABIN%2C%0D%0A%28select+sum%28%22CACTUS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CACTUS%2C%0D%0A%28select+sum%28%22CIRCLE_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CIRCLE_FRAME%2C%0D%0A%28select+sum%28%22CIRRUS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CIRRUS%2C%0D%0A%28select+sum%28%22CLIFF%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CLIFF%2C%0D%0A%28select+sum%28%22CLOUDS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CLOUDS%2C%0D%0A%28select+sum%28%22CONIFER%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CONIFER%2C%0D%0A%28select+sum%28%22CUMULUS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+CUMULUS%2C%0D%0A%28select+sum%28%22DECIDUOUS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+DECIDUOUS%2C%0D%0A%28select+sum%28%22DIANE_ANDRE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+DIANE_ANDRE%2C%0D%0A%28select+sum%28%22DOCK%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+DOCK%2C%0D%0A%28select+sum%28%22DOUBLE_OVAL_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+DOUBLE_OVAL_FRAME%2C%0D%0A%28select+sum%28%22FARM%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FARM%2C%0D%0A%28select+sum%28%22FENCE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FENCE%2C%0D%0A%28select+sum%28%22FIRE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FIRE%2C%0D%0A%28select+sum%28%22FLORIDA_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FLORIDA_FRAME%2C%0D%0A%28select+sum%28%22FLOWERS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FLOWERS%2C%0D%0A%28select+sum%28%22FOG%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FOG%2C%0D%0A%28select+sum%28%22FRAMED%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+FRAMED%2C%0D%0A%28select+sum%28%22GRASS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+GRASS%2C%0D%0A%28select+sum%28%22GUEST%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+GUEST%2C%0D%0A%28select+sum%28%22HALF_CIRCLE_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+HALF_CIRCLE_FRAME%2C%0D%0A%28select+sum%28%22HALF_OVAL_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+HALF_OVAL_FRAME%2C%0D%0A%28select+sum%28%22HILLS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+HILLS%2C%0D%0A%28select+sum%28%22LAKE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+LAKE%2C%0D%0A%28select+sum%28%22LAKES%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+LAKES%2C%0D%0A%28select+sum%28%22LIGHTHOUSE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+LIGHTHOUSE%2C%0D%0A%28select+sum%28%22MILL%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+MILL%2C%0D%0A%28select+sum%28%22MOON%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+MOON%2C%0D%0A%28select+sum%28%22MOUNTAIN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+MOUNTAIN%2C%0D%0A%28select+sum%28%22MOUNTAINS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+MOUNTAINS%2C%0D%0A%28select+sum%28%22NIGHT%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+NIGHT%2C%0D%0A%28select+sum%28%22OCEAN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+OCEAN%2C%0D%0A%28select+sum%28%22OVAL_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+OVAL_FRAME%2C%0D%0A%28select+sum%28%22PALM_TREES%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+PALM_TREES%2C%0D%0A%28select+sum%28%22PATH%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+PATH%2C%0D%0A%28select+sum%28%22PERSON%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+PERSON%2C%0D%0A%28select+sum%28%22PORTRAIT%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+PORTRAIT%2C%0D%0A%28select+sum%28%22RECTANGLE_3D_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+RECTANGLE_3D_FRAME%2C%0D%0A%28select+sum%28%22RECTANGULAR_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+RECTANGULAR_FRAME%2C%0D%0A%28select+sum%28%22RIVER%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+RIVER%2C%0D%0A%28select+sum%28%22ROCKS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+ROCKS%2C%0D%0A%28select+sum%28%22SEASHELL_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+SEASHELL_FRAME%2C%0D%0A%28select+sum%28%22SNOW%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+SNOW%2C%0D%0A%28select+sum%28%22SNOWY_MOUNTAIN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+SNOWY_MOUNTAIN%2C%0D%0A%28select+sum%28%22SPLIT_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+SPLIT_FRAME%2C%0D%0A%28select+sum%28%22STEVE_ROSS%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+STEVE_ROSS%2C%0D%0A%28select+sum%28%22STRUCTURE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+STRUCTURE%2C%0D%0A%28select+sum%28%22SUN%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+SUN%2C%0D%0A%28select+sum%28%22TOMB_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+TOMB_FRAME%2C%0D%0A%28select+sum%28%22TREE%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+TREE%2C%0D%0A%28select+sum%28%22TREES%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+TREES%2C%0D%0A%28select+sum%28%22TRIPLE_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+TRIPLE_FRAME%2C%0D%0A%28select+sum%28%22WATERFALL%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WATERFALL%2C%0D%0A%28select+sum%28%22WAVES%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WAVES%2C%0D%0A%28select+sum%28%22WINDMILL%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WINDMILL%2C%0D%0A%28select+sum%28%22WINDOW_FRAME%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WINDOW_FRAME%2C%0D%0A%28select+sum%28%22WINTER%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WINTER%2C%0D%0A%28select+sum%28%22WOOD_FRAMED%22%29+from+%5Bbob-ross%2Felements-by-episode%5D%29+as+WOOD_FRAMED%3B"&gt;a convoluted Bob Ross example&lt;/a&gt;, returning a count for each of the items that can appear in a painting.&lt;/p&gt;
&lt;p&gt;Datasette has a number of limitations in place here: it cuts off any SQL queries that take longer than a threshold (defaulting to 1000ms) and it refuses to return more than 1,000 rows at a time - partly to avoid too much JSON serialization overhead.&lt;/p&gt;
&lt;p&gt;Datasette also blocks queries containing the string &lt;code&gt;PRAGMA&lt;/code&gt;, since these statements &lt;a href="https://sqlite.org/pragma.html"&gt;could be used to modify database settings at runtime&lt;/a&gt;. If you need to include &lt;code&gt;PRAGMA&lt;/code&gt; in an argument to a query you can do so by constructing a prepared statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select * from [twitter-ratio/senators] where &amp;quot;text&amp;quot; like :q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then construct a URL that incorporates both the SQL and provides a value for that named argument, like this: &lt;a href="https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9?sql=select+rowid%2C+*+from+%5Btwitter-ratio%2Fsenators%5D+where+%22text%22+like+%3Aq&amp;amp;q=%25pragmatic%25"&gt;https://fivethirtyeight.datasettes.com/fivethirtyeight-2628db9?sql=select+rowid%2C+*+from+[twitter-ratio%2Fsenators]+where+“text”+like+%3Aq&amp;amp;q=%25pragmatic%25&lt;/a&gt; - which returns tweets by US senators that include the word “pragmatic”.&lt;/p&gt;
&lt;h2&gt;&lt;a id="Why_an_immutable_API_67"&gt;&lt;/a&gt;Why an immutable API?&lt;/h2&gt;
&lt;p&gt;A key feature of datasette is that the API it provides is very deliberately read-only. This provides a number of interesting benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It lets us use SQLite in production in high traffic scenarios. SQLite is an incredible piece of technology, but it is rarely used in web application contexts due to its limitations with respect to concurrent writes. Datasette opens SQLite files &lt;a href="https://sqlite.org/c3ref/open.html"&gt;using the immutable option&lt;/a&gt;, eliminating any concurrency concerns and allowing SQLite to go even faster for reads.&lt;/li&gt;
&lt;li&gt;Since the database is read-only, we can accept arbitrary SQL queries from our users!&lt;/li&gt;
&lt;li&gt;The datasette API bakes the first few characters of the sha256 hash of the database file contents into the API URLs themselves - for example in &lt;a href="https://parlgov.datasettes.com/parlgov-25f9855/cabinet"&gt;https://parlgov.datasettes.com/parlgov-25f9855/cabinet&lt;/a&gt;. This lets us serve year-long HTTP cache expiry headers, safe in the knowledge that any changes to the data will result in a change to the URL. These cache headers cause the content to be cached by both browsers and intermediary caches, such as Cloudflare.&lt;/li&gt;
&lt;li&gt;Read-only data makes datasette an ideal candidate for containerization. Deployments to Zeit Now happen using a Docker container, and the &lt;code&gt;datasette package&lt;/code&gt; command can be used to build a Docker image that bundles the database files and the datasette application together. If you need to scale to handle vast amounts of traffic, just deploy a bunch of extra containers and load-balance between them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;a id="Implementation_notes_76"&gt;&lt;/a&gt;Implementation notes&lt;/h2&gt;
&lt;p&gt;Datasette is built on top of the &lt;a href="https://github.com/channelcat/sanic"&gt;Sanic&lt;/a&gt; asynchronous Python web framework (see &lt;a href="https://simonwillison.net/2017/Oct/14/async-python-sanic-now/"&gt;my previous notes&lt;/a&gt;), and makes extensive use of Python 3’s async/await statements. Since SQLite doesn’t yet have an async Python module all interactions with SQLite are handled inside a thread pool managed by a &lt;a href="https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor"&gt;concurrent.futures.ThreadPoolExecutor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The CLI is implemented using the &lt;a href="http://click.pocoo.org/"&gt;Click framework&lt;/a&gt;. This is the first time I’ve used Click and it was an absolute joy to work with. I enjoyed it so much I turned one of my Jupyter notebooks into a Click script called &lt;a href="https://github.com/simonw/csvs-to-sqlite"&gt;csvs-to-sqlite&lt;/a&gt; and published it to PyPI.&lt;/p&gt;

&lt;p&gt;This post is &lt;a href="https://news.ycombinator.com/item?id=15691409"&gt;being discussed on a Hacker News&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/json"&gt;json&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sanic"&gt;sanic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/docker"&gt;docker&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="apis"/><category term="cli"/><category term="json"/><category term="projects"/><category term="sqlite"/><category term="webapis"/><category term="sanic"/><category term="docker"/><category term="datasette"/></entry><entry><title>Writing Bulletproof Apps with API Errorpoints</title><link href="https://simonwillison.net/2010/Aug/16/errorpoints/#atom-tag" rel="alternate"/><published>2010-08-16T19:12:00+00:00</published><updated>2010-08-16T19:12:00+00:00</updated><id>https://simonwillison.net/2010/Aug/16/errorpoints/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://blog.programmableweb.com/2010/08/16/writing-bulletproof-apps-with-api-errorpoints/?utm_source=feedburner&amp;amp;utm_medium=feed&amp;amp;utm_campaign=Feed%3A ProgrammableWeb %28ProgrammableWeb%3A Blog%29"&gt;Writing Bulletproof Apps with API Errorpoints&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This is a very good idea: Web APIs should offer special API endpoints for simulating each of the possible errors that might be returned by the production API.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/errors"&gt;errors&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;&lt;/p&gt;



</summary><category term="apis"/><category term="errors"/><category term="webapis"/><category term="recovered"/></entry><entry><title>Facebook Query Language</title><link href="https://simonwillison.net/2007/Feb/25/facebook/#atom-tag" rel="alternate"/><published>2007-02-25T12:06:10+00:00</published><updated>2007-02-25T12:06:10+00:00</updated><id>https://simonwillison.net/2007/Feb/25/facebook/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://developers.facebook.com/documentation.php?v=1.0&amp;amp;doc=fql"&gt;Facebook Query Language&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The Facebook API now lets you run SQL-like queries. You can’t do joins but you can perform very simple subselects.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apis"&gt;apis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/facebook"&gt;facebook&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;&lt;/p&gt;



</summary><category term="apis"/><category term="facebook"/><category term="sql"/><category term="webapis"/></entry><entry><title>W3C Web APIs Working Group</title><link href="https://simonwillison.net/2005/Nov/18/wc/#atom-tag" rel="alternate"/><published>2005-11-18T15:36:37+00:00</published><updated>2005-11-18T15:36:37+00:00</updated><id>https://simonwillison.net/2005/Nov/18/wc/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.w3.org/2006/webapi/"&gt;W3C Web APIs Working Group&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A welcome addition.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="http://blog.dojotoolkit.org/2005/11/17/finally-the-w3-is-participating"&gt;planet::dojo&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/w3c"&gt;w3c&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webapis"&gt;webapis&lt;/a&gt;&lt;/p&gt;



</summary><category term="w3c"/><category term="webapis"/></entry></feed>