<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Skapi</title>
    <description>The latest articles on DEV Community by Skapi (@skapi_api).</description>
    <link>https://dev.to/skapi_api</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3161077%2Ff42727c1-df28-406e-85bd-b25b4a006818.png</url>
      <title>DEV Community: Skapi</title>
      <link>https://dev.to/skapi_api</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/skapi_api"/>
    <language>en</language>
    <item>
      <title>Database built for AI agents</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:53:27 +0000</pubDate>
      <link>https://dev.to/skapi_api/database-built-for-ai-agents-o3k</link>
      <guid>https://dev.to/skapi_api/database-built-for-ai-agents-o3k</guid>
      <description>&lt;h2&gt;
  
  
  No schema, no setup, no scans: how the Skapi database works, and why it is built for AI agents
&lt;/h2&gt;

&lt;p&gt;The question we get more than any other is some version of "how do I set up my database?" Where is the schema designer. Where do I define the tables. What about indexes, migrations, the query planner. The answer is that you do not. There is nothing to set up. You write a record, and the table exists. You delete the last record, and the table is gone. If that sounds like something that cannot possibly hold up under real load, this article is for you. It explains the model behind the Skapi database, schema-free and index-on-write: why it does the work you reach for a relational database to do, why it never falls into the full-table scan that quietly drains production budgets, why the same mechanism is what keeps your data secure, and why it is the right shape for a world where AI agents write most of the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" alt="Skapi" width="244" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The question, answered once
&lt;/h2&gt;

&lt;p&gt;A Skapi project has no database to set up, because there is no moment at which structure is decided ahead of data.&lt;/p&gt;

&lt;p&gt;Tables are not created. A table appears when the first record is written into it and disappears when the last one leaves. Indexes are not defined. An index is an argument you pass when you write a record. Access rules are not configured. They are an argument too. There are no migrations, because there is nothing to migrate: a record written today with a new field does not care what the record next to it looks like, and the database does not care either.&lt;/p&gt;

&lt;p&gt;So when you ask "how should I design my schema", the honest answer is this. Write the records your product needs, and say, on each one, how you want to find it again. That is the whole discipline. Everything below is about why that is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the confusion comes from
&lt;/h2&gt;

&lt;p&gt;If you come from traditional backend engineering, you have been trained on two ways a database decides what data looks like.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;schema-on-write&lt;/strong&gt;. You design the structure before any data exists: tables, columns, types, constraints, indexes. The database refuses anything that does not fit. This is the relational model, and it is very good at protecting the integrity of data. What it does not protect is the cost of reading it. Every query is handed to a planner at runtime, and the planner decides how to find the rows. If your condition happens to be covered by an index, it seeks. If it is not, it scans the whole table. Both are legal. Nothing warns you. The scan is the silent default for any question nobody anticipated when the schema was drawn.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;schema-on-read&lt;/strong&gt;. You store whatever arrives and decide what it means when you query it. This is the data lake, and it is also what a document store becomes when its indexes are an afterthought. There is no structure to maintain, which is pleasant, and every read is a scan by definition, which is not.&lt;/p&gt;

&lt;p&gt;Both models share one trait. The cost of a read is decided at read time, by machinery you do not see, over data you wrote earlier without saying how you would read it. The expensive decision is made at the moment you have the least information and the least control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index-on-write
&lt;/h2&gt;

&lt;p&gt;Skapi moves that decision to the one moment you know the most about a record: when you write it.&lt;/p&gt;

&lt;p&gt;When you post a record, you say a few things alongside the data. Which table it belongs to. Which access group may read it. Optionally, one index name and value to find it by. Optionally, tags. Optionally, the record it references. The database stores the record under keys built from those declarations, in sorted order, typed so that numbers sort as numbers and strings as strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dukkha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asian Spice House&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Indie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Experimental&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the schema. It is local to this record, it was decided by the code that had the record in hand, and it is already applied by the time the write returns.&lt;/p&gt;

&lt;p&gt;A query is the mirror image: a table, an access group, and at most one index condition or range.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every such query maps to one contiguous range of sorted keys. The read is a seek to the start of the range and a walk to the end of it, a page at a time. Nothing outside the range is touched. There is no planner, because there is nothing to plan.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflfltu0p868xx81phop8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fflfltu0p868xx81phop8.png" alt="query maps" width="800" height="320"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One record, written once. The table, access group and index you pass become a sorted key, and a query reads one range of those keys. Rows outside the range are never touched.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Structure is decided&lt;/th&gt;
&lt;th&gt;Access path is decided&lt;/th&gt;
&lt;th&gt;What a read costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schema-on-write (relational)&lt;/td&gt;
&lt;td&gt;Before any data, for the whole table&lt;/td&gt;
&lt;td&gt;At query time, by the planner&lt;/td&gt;
&lt;td&gt;A seek if an index happens to cover it, a scan if not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schema-on-read (lakes, loose document stores)&lt;/td&gt;
&lt;td&gt;At query time&lt;/td&gt;
&lt;td&gt;At query time&lt;/td&gt;
&lt;td&gt;A scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Index-on-write (Skapi)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per record, when it is written&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;When it is written&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;One key range, proportional to the result&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  The expensive query cannot be written
&lt;/h2&gt;

&lt;p&gt;Here is the theory in one line. A scan costs in proportion to the size of the table. A seek costs in proportion to the size of the result. Everything that goes wrong with database bills lives in the gap between those two.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fypfh2a0b8mda7vusngn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fypfh2a0b8mda7vusngn3.png" alt="database bills" width="799" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A scan reads every row, so its cost grows with the table. A seek reads the matching range, so its cost grows with the result. On Skapi, only the seek exists.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every experienced engineer has seen the pattern. A query runs fine in development against ten thousand rows. It ships. The table grows to fifty million rows, and the same query is now five thousand times more expensive, because it was always a scan and nobody noticed while the table was small. On a database billed per read, that is not a slow page. That is an invoice. The fix is an index that someone had to think of in advance, which is exactly the thinking the schema was supposed to force, and did not.&lt;/p&gt;

&lt;p&gt;On Skapi the scan is not slow. It does not exist. There is no way to ask the database for "every record whose data contains some value", because the data is not what the database reads. The keys are. If you want to find records by a value, you index on that value when you write. If you did not, the query is not expensive, it is not a query.&lt;/p&gt;

&lt;p&gt;The consequence is that the cost of any read is proportional to what it returns, not to how much you have stored. A table of ten records and a table of ten million answer the same query at the same cost per record returned. There is no slow-query log to read, no planner to second-guess, and no index advisor telling you what you should have done last month. A junior developer and a senior one write the same query, because there is only one kind of query to write.&lt;/p&gt;
&lt;h2&gt;
  
  
  Everything you build a relational database for
&lt;/h2&gt;

&lt;p&gt;The objection at this point is that a single index per record cannot possibly cover what a relational database does. It covers what you actually build. Every item below is a key read, and each one is a few lines of skapi-js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find by id.&lt;/strong&gt; Every record has an id, and you can give it your own: an order number, a slug, a SKU. Both are direct lookups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-1042&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;unique_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order-1042&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;record_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record_id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Newest first, oldest first.&lt;/strong&gt; Every record is ordered by when it was uploaded and when it was last updated. Both are reserved index names, so "everything from the last seven days, newest first" is a range read.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$uploaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Conditions and ranges.&lt;/strong&gt; Equal, greater, greater or equal, less, less or equal, and a range between two values. On a string, the same operators give you "starts with" and "ends with".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// albums released from 1960 to 1970&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1970&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// titles that end with "Gilberto" ('&amp;gt;=' on a string is "starts with")&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Gilberto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Relations.&lt;/strong&gt; A record can reference another record. Comments reference a post, order items reference an order, a like references what was liked. Fetch everything that references a record, and the relationship is already there. A join is the engine discovering a relationship at query time. A reference is the relationship recorded at write time, which is the only moment anyone actually knew it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What do you think?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I like it!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record_id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// every comment on that post&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Comments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;record_id&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4fzzfenruichjuaevt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4fzzfenruichjuaevt0.png" alt="post is written once" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A post is written once. Each comment and like points at it, and the post carries a count of how many records point at it. Fetching by reference reads those records and nothing else.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Counting relations.&lt;/strong&gt; How many records reference a record is itself an index. "The most commented posts" is a range read, not a GROUP BY over a comments table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// posts with more than ten comments, most commented first&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$referenced_count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ascending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Hierarchy.&lt;/strong&gt; A compound index name such as &lt;code&gt;Band.AsianSpiceHouse.year&lt;/code&gt; can be queried at any level: every band, bands by name, or one band's albums by year. Here is the write, and the index part of three queries on the same table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Band.AsianSpiceHouse.year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// every album by a band&lt;/span&gt;
&lt;span class="nl"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Band.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// bands whose name starts with "Asian"&lt;/span&gt;
&lt;span class="nl"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Band.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Asian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// one band's albums released after 2010&lt;/span&gt;
&lt;span class="nl"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Band.AsianSpiceHouse.year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tags.&lt;/strong&gt; Labels that cut across indexes. A tag narrows any query, and every tag carries its own record count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Indie&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Experimental&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// albums released after 2020 that are tagged Experimental&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;year&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Experimental&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Aggregates without scanning.&lt;/strong&gt; Record counts per table, per tag and per index. Totals and averages of numeric indexes. The rate of true values for boolean indexes. All of it is kept up to date as records are written, so a rating board does not average votes at read time. The average is already there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// each vote is a record with a boolean index&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VoteBoard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vote.Beer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIndexes&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VoteBoard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vote.Beer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// average_bool: the share of true votes, boolean_count: how many voted&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTables&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// number_of_records and size, per table&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTags&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Albums&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// every tag in the table with its number_of_records&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Per user.&lt;/strong&gt; Every user's records, per table, and subscriptions and feeds on top of that. A user follows another user, and reads one feed of everything the people they follow have posted to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// everything one user posted to a table&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Posts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user_id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// follow a user, then read the feed of everyone you follow&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;get_feed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFeed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what is not on the list: anything that would require reading the whole table to answer. That is not an omission. That is the point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permission is an address, not a filter
&lt;/h2&gt;

&lt;p&gt;On most backends, access control is a filter. A policy is a predicate added to each query and evaluated per row by the same engine that may or may not have an index for it. It is a step in the read, and like any step it can be slow, and it can be skipped by any path that writes to the database without going through it.&lt;/p&gt;

&lt;p&gt;On Skapi, the access group is part of the key a record is stored under. Public records, authorized records, numbered groups from 0 to 99, admin, private: each is a different address. A query names the group it wants to read. A user in access group 5 can ask for group 5 or any group below it, and the read lands on that group's keys alone. A user in group 3 who asks for group 5 is refused before a single key is read. The rows for group 50 are not filtered out of anyone's result. They are never read, because they do not live where the query is looking. Private records are reachable only by their uploader and by users the uploader explicitly grants, and that grant is an address too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha0dzdkdpfygkha2tmhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fha0dzdkdpfygkha2tmhc.png" alt="grant is an address" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A query names the access group it wants. The read lands on that group's address and nowhere else. Higher groups and private records are not filtered out of the result. They are never read.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is why security and cost come from the same mechanism. A permission check is not a stage that a badly written query can bypass or a busy engine can make expensive. It is where the data is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sealed in the browser
&lt;/h2&gt;

&lt;p&gt;The model goes one step further for the data a user keeps to themselves. Turn on record encryption, and the data of a private record is encrypted in the browser before it leaves the device, with keys derived from the user's own password that never leave that device. A provider who dumps the database, the storage bucket and every request log cannot recover the contents without guessing the user's password.&lt;/p&gt;

&lt;p&gt;Index-on-write is what makes this possible without breaking the database. The database reads structure: table, access group, index, tags, reference. It stores content. Because those are separate, the structure can stay readable, so every query above keeps working, while the content is sealed. The same is true of attached files: their bytes are encrypted, their names and sizes are not. The details, and the exact security claim, are in the &lt;a href="https://docs.skapi.com/database/encryption.html" rel="noopener noreferrer"&gt;encryption guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the database for AI agents
&lt;/h2&gt;

&lt;p&gt;Agents write code fast and in volume, and they write it against a backend you did not fully review. A database has to be safe under that kind of writer.&lt;/p&gt;

&lt;p&gt;Against a schema-on-write database, every change an agent makes is global. One more field means a migration, a policy per operation, regenerated types and a redeploy, and the loop starts again on the next request. We &lt;a href="https://www.skapi.com/articles/2026-10-06-00" rel="noopener noreferrer"&gt;measured that loop&lt;/a&gt; in tokens. The deeper reason it costs so much is that a schema is shared state: an agent must hold the whole of it in context to change any of it safely.&lt;/p&gt;

&lt;p&gt;Index-on-write has no shared state. Every decision is local to one write: this record, this table, this index, this access group. The agent decides at the point of maximum information and never has to reconcile the decision with anything else. When it needs to know the shape of the data, it asks the database, which lists tables, indexes and tags with their counts, maintained as the data was written. The schema is discovered from the data instead of being maintained beside it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx9e4jor6btc6xln6ymh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx9e4jor6btc6xln6ymh5.png" alt="schema on-write" width="800" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Three agents changing a schema-on-write backend all pass through one shared schema and the migrate, policies, types, deploy loop. On index-on-write, each agent writes its own record with its own rules, and nothing has to be reconciled.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And the cost guard holds for agents exactly as it holds for people. Agents will write queries you never read. On a relational database, one of them will be a scan. On Skapi it cannot be, because the only query that exists is a key read. The worst an agent can do is fetch a page it did not need.&lt;/p&gt;

&lt;p&gt;For fifty years, database design has meant deciding structure before data and paying for every question nobody anticipated. Agents invert the workflow. Data first, structure as a property of each record, access as part of the address, and a database that stays cheap and safe no matter who, or what, is writing to it. That is index-on-write, and it is what the Skapi database has been from the beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a record
&lt;/h2&gt;

&lt;p&gt;Create a Free project, write one record from the browser, and you have a database. The &lt;a href="https://docs.skapi.com/database/create.html" rel="noopener noreferrer"&gt;database guide&lt;/a&gt; shows every argument from the example above, and there is nothing to set up before you begin.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>database</category>
      <category>backend</category>
    </item>
    <item>
      <title>Newsletters on Skapi: simpler than Mailchimp</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 07:46:39 +0000</pubDate>
      <link>https://dev.to/skapi_api/newsletters-on-skapi-simpler-than-mailchimp-4ieb</link>
      <guid>https://dev.to/skapi_api/newsletters-on-skapi-simpler-than-mailchimp-4ieb</guid>
      <description>&lt;h2&gt;
  
  
  Newsletters on Skapi: simpler than Mailchimp, for a fraction of the price
&lt;/h2&gt;

&lt;p&gt;A marketing newsletter needs three things: a list, a form that fills it, and a way to send. Skapi ships all three inside the backend you already pay for, and the sending part is the mail app you already have. Here is how it works, click by click, and what it costs next to Mailchimp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" alt="Skapi" width="244" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One page, one address
&lt;/h2&gt;

&lt;p&gt;Open your project and choose &lt;strong&gt;Newsletters&lt;/strong&gt; in the sidebar. The page has a tab per list: &lt;strong&gt;Newsletter&lt;/strong&gt; is the public list anyone can join with an email address, &lt;strong&gt;Service Email&lt;/strong&gt; is the list of your signed-in users, and every named group you create gets a tab of its own.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-page.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-page.webp" alt="The Newsletters page of a Skapi project: tabs per list, the sending address, monthly sends, subscribers, and the table of sent newsletters" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Newsletters page. Every list has its own sending address, its own subscriber count and its own history of sent mail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first row is the whole sending story. Write your newsletter in your own mail app, the one you use every day, and send it to the address shown there. &lt;strong&gt;[Send]&lt;/strong&gt; opens a new message to that address for you. Skapi forwards the email to every subscriber of that list, adds an unsubscribe link at the bottom, and the table below fills in with the subject, the date, and how many people read it, complained, or bounced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-compose.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-compose.webp" alt="A mail compose window: From the project owner's address, To the newsletter sending address, a subject reading Spring menu is here, a short newsletter with a list of items, a link and an attached image, and a Send button" width="800" height="743"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composing a newsletter in a mail app. The address in To is the one the Newsletters page shows, and the email goes out as written, with an unsubscribe link added at the bottom.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is no editor to learn, no template gallery, no campaign builder. If you can write an email, you can send a newsletter.&lt;/p&gt;

&lt;p&gt;Two guardrails sit behind that address. Mail is only accepted from the email address your project is registered with, so knowing the address is not enough to send as you. And the sending address has to pass the sender trust checks (SPF, DKIM and DMARC), which the large email providers already do. Anything else is refused, and Skapi replies to tell you why.&lt;/p&gt;

&lt;p&gt;Before any of this works, register an &lt;strong&gt;email alias&lt;/strong&gt; on your project's Settings page. It becomes the address your newsletters and automated emails are sent from, and the Newsletters page stays closed until you have one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding subscribers by hand
&lt;/h2&gt;

&lt;p&gt;Not every subscriber comes through a form. You may be moving a list from another tool, or you may have customers who wrote their address on a sheet at the counter. On the &lt;strong&gt;Subscribers&lt;/strong&gt; row, click &lt;strong&gt;[Add]&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-add.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-add.webp" alt="The Add Subscribers dialog with three pasted addresses staged and a button reading Add 3 subscribers" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Paste addresses, one per line or separated by commas, check the list, and add them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Paste the addresses, one per line or separated by commas, and click &lt;strong&gt;Add to list&lt;/strong&gt;. Anything malformed is set aside and shown to you, duplicates are folded together, and the addresses that remain are listed so you can remove one before committing. Then &lt;strong&gt;Add 3 subscribers&lt;/strong&gt;, or however many you pasted.&lt;/p&gt;

&lt;p&gt;Addresses added this way are subscribed right away, without a confirmation mail, so use it for people who have already asked to hear from you. Everyone else should come in through the form below, which confirms the address first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[View]&lt;/strong&gt; opens the list itself, with a search that matches the start of an address.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-subscribers.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-subscribers.webp" alt="The Subscribers dialog listing addresses with a search box and paging" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The subscriber list, searchable by the start of an address.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Service Email&lt;/strong&gt; tab has no &lt;strong&gt;[Add]&lt;/strong&gt;. Its subscribers are your users, who subscribe and unsubscribe with their own account, so nobody can be added to it by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Groups: several lists on one project
&lt;/h2&gt;

&lt;p&gt;One project is not limited to one newsletter. Click &lt;strong&gt;+ New group&lt;/strong&gt; to create a named list.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-group.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-group.webp" alt="The New Group dialog with a name, a label and a Subscribers choice of Anyone, Any signed in user or Access group and above" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A named group: a short name that becomes part of its sending address, an optional label for the tab, and who may subscribe.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;name&lt;/strong&gt; is two to twenty lowercase letters and digits. It becomes part of the group's sending address and cannot change later. The &lt;strong&gt;label&lt;/strong&gt; is what the tab shows. &lt;strong&gt;Subscribers&lt;/strong&gt; decides who may join: anyone with an email address, any signed-in user of your app, or only users at or above an access group you choose. That last option is how you run a members-only or paying-customers-only list without writing any code.&lt;/p&gt;

&lt;p&gt;Each group keeps its own subscribers, its own sending address and its own sent mail, so a subscriber of one never receives the other. A project can hold up to twenty of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-named-group.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-named-group.webp" alt="The Promotions group tab showing its own sending address, its subscriber count, an Access row reading Anyone can subscribe, and a Delete group link" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A named group has its own address, its own count and an Access row. Deleting the group deletes its subscribers with it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The signup form, without writing it
&lt;/h2&gt;

&lt;p&gt;The form is the part most people expect to build. You do not have to. Open your project's &lt;strong&gt;Settings&lt;/strong&gt; page and scroll to &lt;strong&gt;Application Examples&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-examples.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-examples.webp" alt="The Application Examples card on the Settings page, with a Newsletter row and an Open link" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Application Examples on the Settings page. Every row opens a plain HTML page that already runs against your project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;[Open]&lt;/strong&gt; on the &lt;strong&gt;Newsletter&lt;/strong&gt; row. What opens is a working subscription page, already pointed at your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-form.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fnewsletter-form.webp" alt="The example newsletter page: a short explanation, an email field and a Subscribe button" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The example page. Its address carries your project id, so it can be linked as it is.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can use it three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Link it.&lt;/strong&gt; The address of that page carries your project id, so a link from your site, your Instagram bio or your email signature is a working signup form. Nothing to host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy the form.&lt;/strong&gt; Click &lt;strong&gt;[Download]&lt;/strong&gt; on the same card for every example page as a ZIP, open &lt;code&gt;newsletter.html&lt;/code&gt;, and paste the form into your own page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write it.&lt;/strong&gt; It is a form with two fields, passed straight to the SDK:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;onsubmit=&lt;/span&gt;&lt;span class="s"&gt;"skapi.subscribeNewsletter(event).then(res =&amp;gt; alert(res))"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"your@email.com"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"group"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"public"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"redirect"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"https://your.domain.com/thanks"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Subscribe"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change &lt;code&gt;public&lt;/code&gt; to a group's name to feed a named group instead, and &lt;code&gt;redirect&lt;/code&gt; is where the visitor lands after confirming.&lt;/p&gt;

&lt;p&gt;A visitor who subscribes this way gets a confirmation email and joins the list when they click the link in it, so every address on your public list is one that proved it wanted to be there. A signed-in user with a verified email skips the confirmation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules that keep you out of the spam folder
&lt;/h2&gt;

&lt;p&gt;The plumbing that a mailing tool charges for is switched on for every project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Confirmation&lt;/strong&gt; for every public signup, and an &lt;strong&gt;unsubscribe link&lt;/strong&gt; on every newsletter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complaint shutoff.&lt;/strong&gt; A project that collects five spam complaints in a month, or a complaint rate of half a percent once a hundred mails have gone out, stops sending newsletters. Nothing else about the project stops, and we clear the block after a look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sender trust.&lt;/strong&gt; Only mail from your registered address, passing SPF, DKIM and DMARC, is forwarded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counted per recipient.&lt;/strong&gt; One newsletter to a list of 1,000 uses 1,000 of your monthly sends, so the number on your plan is a number of deliveries, not of campaigns.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs
&lt;/h2&gt;

&lt;p&gt;Newsletters are part of every plan, not a separate product.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Free&lt;/th&gt;
&lt;th&gt;Standard $19/mo&lt;/th&gt;
&lt;th&gt;Premium $89/mo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subscribers, all groups&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Newsletter sends per month&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email storage&lt;/td&gt;
&lt;td&gt;50 MB&lt;/td&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Past a limit&lt;/td&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;$1 per 1,000 extra sends or subscribers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same plan carries your database, file storage, hosting, user accounts, automated emails and everything else a Skapi project does. The newsletter is not the thing you are paying for. It comes with it.&lt;/p&gt;

&lt;p&gt;Now Mailchimp, from its pricing page at the time of writing. Its Free plan takes 250 contacts and 500 sends a month. The paid plans start at $13 a month for Essentials and $20 for Standard, both for up to 500 contacts, and the price climbs with every tier of contacts you hold. Premium starts at $350.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contacts&lt;/th&gt;
&lt;th&gt;Mailchimp Essentials&lt;/th&gt;
&lt;th&gt;Mailchimp Standard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;$13&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2,500&lt;/td&gt;
&lt;td&gt;$45&lt;/td&gt;
&lt;td&gt;$60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;td&gt;$75&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;$110&lt;/td&gt;
&lt;td&gt;$135&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25,000&lt;/td&gt;
&lt;td&gt;$270&lt;/td&gt;
&lt;td&gt;$310&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50,000&lt;/td&gt;
&lt;td&gt;$385&lt;/td&gt;
&lt;td&gt;$450&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Put the two side by side for a monthly newsletter, one send to the whole list each month:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kcbktc3tyzojkh4idrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kcbktc3tyzojkh4idrp.png" alt="Mailchimp cheaper alternative" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$19 for 5,000 subscribers, against $75 to $100. $89 for 50,000, against $385 to $450.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same list at both companies, at the time of writing. Skapi's price is the plan the list fits in, Standard up to 5,000 subscribers and Premium above it, and that plan runs the rest of your backend too. Mailchimp's is its published tier for that many contacts.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two honest notes. Below a few hundred subscribers, Mailchimp's entry tier is a few dollars cheaper than our Standard plan, and its free tier is larger than ours. And Skapi counts sends per recipient, so a weekly newsletter to 5,000 people is 20,000 sends a month, which puts you on Premium at $89, about what Mailchimp charges for the same list. At 25,000 subscribers sent weekly, Skapi is $139 with the extra sends billed, against $270 and up. The bigger the list, the wider the gap.&lt;/p&gt;

&lt;p&gt;One more difference in how the count works. Mailchimp's tiers are based on the contacts in your audience, which by its own rules includes people who have unsubscribed until you archive them. Skapi counts subscribers. When someone unsubscribes, they leave the list and the count.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Mailchimp has that we do not
&lt;/h2&gt;

&lt;p&gt;Mailchimp is a marketing suite. It has a drag-and-drop editor, a template gallery, automation journeys, A/B tests, landing pages and deep reporting. If your business runs on those, keep it.&lt;/p&gt;

&lt;p&gt;Skapi gives you a list, a form, an address, and a table that tells you who read, who complained and who bounced. You write in your own mail app, and segmentation is a group. For a great many businesses, that is the entire newsletter they ever wanted, and it is the part that Mailchimp charges for by the contact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Register an email alias on your project's Settings page.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Newsletters&lt;/strong&gt;, add the subscribers you already have with &lt;strong&gt;[Add]&lt;/strong&gt;, and create a group if you need more than one list.&lt;/li&gt;
&lt;li&gt;Link the example signup page, or paste its form into your site.&lt;/li&gt;
&lt;li&gt;Write the newsletter in your mail app and send it to the address on the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first one takes about ten minutes. Every one after that takes as long as writing the email.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>automation</category>
      <category>startup</category>
      <category>tools</category>
    </item>
    <item>
      <title>How to cut the token bill of vibe coding: Skapi, backend built for AI agents</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:34:46 +0000</pubDate>
      <link>https://dev.to/skapi_api/how-to-cut-the-token-bill-of-vibe-coding-skapi-backend-built-for-ai-agents-33jm</link>
      <guid>https://dev.to/skapi_api/how-to-cut-the-token-bill-of-vibe-coding-skapi-backend-built-for-ai-agents-33jm</guid>
      <description>&lt;h2&gt;
  
  
  Cut the token bill of vibe coding: Skapi, backend built for AI agents
&lt;/h2&gt;

&lt;p&gt;Every prompt you send an AI coding agent costs tokens, and a surprising share of them go to the backend: schemas, policies, generated types, redeploys. We measured where the tokens go when the backend is built through an MCP server on Supabase or Firebase, why the spending never stops after the first build, and what the same app costs when it runs on skapi-js instead. We measured our own side the same way: the prompt file the agent loads, the pages it reads, and the Skapi MCP server. The short version: Skapi is the backend that costs your agent the least, because there is no schema to rebuild, ever.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" alt="Skapi" width="244" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the tokens go
&lt;/h2&gt;

&lt;p&gt;An AI coding agent works in round trips. It reads its context, calls a tool, reads the result, and calls again. Every one of those trips sends the whole context back through the model: the system prompt, the instruction file your tool loads from the project root, the conversation so far, and the description of every tool the agent is allowed to use. Prompt caching makes the repeated part cheaper, usually to about a tenth of the price, but it never makes it free, and cache entries expire in minutes.&lt;/p&gt;

&lt;p&gt;Build a backend through an MCP server and the loop looks like this. The agent lists the tables to learn the schema, writes a migration, applies it, writes a row-level security policy per operation per table, regenerates the TypeScript types, runs the security advisor, fixes what it reports, and only then edits the client code. Then you ask for one more field, and the loop runs again from the migration step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flve0gmnfjigwe20jnjfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flve0gmnfjigwe20jnjfw.png" alt="backend through MCP" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The loop an agent runs for every schema change, and the loop it runs on skapi-js. The steps in the first row are the tools Supabase's MCP server describes for exactly this purpose.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two things make the first loop expensive, and neither shows up on a pricing page. The steps are round trips, so the growing context is re-read at each one. And the artifacts are not small: a migration is hundreds of tokens, the policies are hundreds more, and the regenerated types file is re-read in full after every change.&lt;/p&gt;

&lt;h2&gt;
  
  
  We measured it
&lt;/h2&gt;

&lt;p&gt;The numbers come from the real servers and the real files. We started the &lt;code&gt;@supabase/mcp-server-supabase&lt;/code&gt; (0.13.0) and &lt;code&gt;firebase-tools&lt;/code&gt; (15.31.0) packages over stdio, sent &lt;code&gt;tools/list&lt;/code&gt;, and counted the payload the agent receives with the cl100k tokenizer. We counted Supabase's published AI prompt files the same way, from the &lt;code&gt;examples/prompts&lt;/code&gt; folder of its repository on 2026-10-07, and on the Skapi side the prompt file, the documentation pages as published that day, and our own MCP server's &lt;code&gt;tools/list&lt;/code&gt;. Claude's tokenizer differs a little, so read these as sizes, not invoices. The migration, policies and types on the Supabase side are our own minimal examples for a task app with &lt;code&gt;profiles&lt;/code&gt;, &lt;code&gt;projects&lt;/code&gt; and &lt;code&gt;tasks&lt;/code&gt;, written the way Supabase's own guides say to write them.&lt;/p&gt;

&lt;h3&gt;
  
  
  What travels with every request
&lt;/h3&gt;

&lt;p&gt;Two things ride along on every round trip for as long as the session lasts: the tool list an MCP server describes to the model, and the instruction file your coding tool loads from the project root (&lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.cursorrules&lt;/code&gt; and the rest). Supabase's tool list alone is 5,051 tokens in its default configuration, and 1,051 with only the database and development tools, but its documentation tells you to add its prompt files to that instruction file, for RLS policies, migrations, the declarative schema, the SQL style guide and the auth bootstrap, because the agent writes them wrong without the rules. Those eight files are 12,563 tokens; the five a task app needs are 6,552. On the Skapi side there is no server in the build loop, so the standing cost is the instruction file alone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2dhz5apkz9rafh09qm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2dhz5apkz9rafh09qm6.png" alt="Skapi is serverless" width="799" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Compact JSON as the client receives it, plus the prompt files each side tells you to load. Firebase's number is large because its Firestore document tools carry their full JSON schemas. The Supabase rows are its default tool list plus its own published prompt files, all eight or the five a task app needs. The two Skapi rows are the same session: 5,847 tokens of prompt file, and no tool list, because the agent builds against the SDK, not through a server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Firebase's &lt;code&gt;--only&lt;/code&gt; flag and Supabase's &lt;code&gt;features&lt;/code&gt; parameter cut those lists down, and you should use them. But the smallest Supabase set that can still apply a migration is about a thousand tokens, most people run the default, and the prompt files do not shrink with the flag. Take a modest working session of 40 tool calls. On Supabase, with the default list and the prompt files a task app needs, that is about 460,000 tokens of standing context passing through the model. On Firebase's default list it is 2.4 million before any instruction file. On Skapi it is about 230,000, and that is everything: the rules, the map and the whole method index.&lt;/p&gt;

&lt;p&gt;The Skapi row is the whole instruction file: &lt;code&gt;SKAPI.md&lt;/code&gt; is 24.7 KB, 5,847 tokens, and it holds the rules, a map of the documentation and an index of every SDK method, each linking to its page. It is sized to be loaded on every turn by every tool, Codex CLI's 32 KiB limit on &lt;code&gt;AGENTS.md&lt;/code&gt; included, and the pages themselves are read only when a feature needs them, which is the next number.&lt;/p&gt;

&lt;h3&gt;
  
  
  The change loop, three steps
&lt;/h3&gt;

&lt;p&gt;Now the artifacts. We built the same task app three times over: the first build, then "add a priority to tasks and filter by it", then "let me share a task with a teammate". For each step we counted what the agent has to write for the backend and what it has to read back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbtitwdfhroq1t1lxbxay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbtitwdfhroq1t1lxbxay.png" alt="Artefacts" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Each Supabase bar is what one step costs in backend artifacts: the first segment is what the agent writes, the second is the regenerated types file it reads back afterwards. Three steps come to 6,705 tokens, and 5,253 of them, 78 percent, are the same types file read back three times. The skapi-js rows have no bar to draw.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Supabase over MCP&lt;/th&gt;
&lt;th&gt;skapi-js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Build&lt;/strong&gt;: accounts, tasks with an attachment&lt;/td&gt;
&lt;td&gt;Migration with 3 tables, 3 indexes, RLS on every table and 12 policies: 787 tokens. Storage bucket and 3 object policies: 159. Regenerated types, read back: 1,751. &lt;strong&gt;2,697 tokens&lt;/strong&gt;, plus &lt;code&gt;list_tables&lt;/code&gt; and &lt;code&gt;get_advisors&lt;/code&gt; responses we could not measure without a live project.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt;. Accounts come with the project. A record carries its own table name, access group and index, so there is nothing to create before the first &lt;code&gt;postRecord()&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Change 1&lt;/strong&gt;: add a priority, filter by it&lt;/td&gt;
&lt;td&gt;Migration adding the column and an index: 38. Regenerated types, read back in full: 1,751. &lt;strong&gt;1,789 tokens&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt;. The change is an &lt;code&gt;index&lt;/code&gt; key in the call that already exists: about 100 tokens of client edit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Change 2&lt;/strong&gt;: share a task with a teammate&lt;/td&gt;
&lt;td&gt;New &lt;code&gt;task_shares&lt;/code&gt; table, its RLS, and the two task policies rewritten with &lt;code&gt;exists&lt;/code&gt; subqueries: 468. Regenerated types: 1,751. &lt;strong&gt;2,219 tokens&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;0&lt;/strong&gt;. One call: &lt;code&gt;skapi.grantPrivateRecordAccess({ record_id, user_id })&lt;/code&gt;, 28 tokens.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The client code is roughly the same shape on both sides. Our supabase-js module for the build step is 408 tokens, the skapi-js one 202. The difference is not the client. It is everything the client needs to exist first.&lt;/p&gt;

&lt;p&gt;Notice the pattern in the middle column. The migration for change 1 is 38 tokens. The regenerated types the agent then reads back are 1,751, and they are 1,751 again after change 2, and after every change after that, because the tool returns the whole file. The steady cost of a schema is not the schema. It is the re-reading.&lt;/p&gt;

&lt;p&gt;And these are the artifacts alone. Each of those steps is a tool call, and each tool call is a round trip that re-reads the conversation and the tool list. A change that costs 1,789 tokens of artifacts costs several times that by the time the agent has listed the tables, applied the migration, regenerated the types, run the advisor and read every response.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent reads instead
&lt;/h2&gt;

&lt;p&gt;The fair question is what the agent reads on the Skapi side, since it is not reading a tool list. It reads documentation, once per page, when the feature comes up. The prompt file tells it which page to read: every page of the documentation is published as raw markdown under &lt;code&gt;docs.skapi.com/md/&lt;/code&gt;, 94 pages, and the file links to the 71 on the sidebar; the rest are reached through the links between pages.&lt;/p&gt;

&lt;p&gt;For the task app above the agent reads ten guide pages: getting started, signing up and logging in, creating, fetching, updating and deleting records, access restrictions, files and indexing. Measured on the published files, they come to 25,401 tokens. The reference entries it is told to check before writing a call live on three more pages, authentication, database and data types, 23,718 tokens if it reads all three in full, and the data types page is 11,280 of that. So the most an agent reads for this app is about 49,000 tokens, read once, and a careful agent that reads the entries it needs rather than whole pages reads less. The median sidebar page is 2,154 tokens.&lt;/p&gt;

&lt;p&gt;The difference is what happens after the first read. On Skapi, nothing in the documentation changes when your app changes, so it is never re-read. Every schema change on the other side produces a new artifact that the agent has to read back. An agent building on Supabase reads its guides too; the RLS guide alone is the one that tells it to write a policy per operation per table. We did not measure those reads, so they are not in the charts, but they are not zero either.&lt;/p&gt;

&lt;p&gt;Put the session together and it reads like this. On Skapi: 5,847 tokens of prompt file on every turn, the pages the app needs once, and no backend artifact, ever. Through an MCP server: the tool list on every turn, the guides once, and a migration, a policy set and a regenerated types file on every change.&lt;/p&gt;

&lt;p&gt;Skapi's prompt file is what the dashboard downloads under the name your tool expects, &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;.cursorrules&lt;/code&gt; and the rest. Open your project's Settings page and look for &lt;strong&gt;For AI Agents&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-agents-card.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-agents-card.webp" alt="The For AI Agents card on a project's Settings page: the opening prompt with the project ID filled in, and a Download row per tool from Claude Code to Windsurf" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The For AI Agents card. Click the prompt to copy it, then download the file under the name your agent reads.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And the Skapi MCP server
&lt;/h2&gt;

&lt;p&gt;Skapi has an MCP server too, and it is useful: it lets an agent manage your project, read and write records, register tickets, store secret keys and set up OpenID login, signed in as you. Measured the same way as the others, its public &lt;code&gt;tools/list&lt;/code&gt; is 17 tools and 7,610 tokens, in the same range as Supabase's default list. It is simply not part of the build loop. The agent writes code against &lt;code&gt;skapi-js&lt;/code&gt;, and the backend answers; the server is for the moments when you want the agent to operate the project rather than write the app. It carries no documentation of its own: when an agent on a platform that cannot fetch urls needs a page, the server's &lt;code&gt;readDocs&lt;/code&gt; tool returns that one page from the same source.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the meter is credits instead of tokens
&lt;/h2&gt;

&lt;p&gt;Web builders such as Lovable do not show you tokens. They charge credits per message, and the price of a message depends on how much work the agent did. At the time of writing, Lovable's own examples put "Make the button gray" at 0.50 credits and "Add authentication with sign up and login" at 1.20, the free plan grants 5 build credits a day, and the entry Pro tier is $25 for 100 credits a month, so a message that makes the agent do more costs you more of them.&lt;/p&gt;

&lt;p&gt;The meter is different, the mechanism is not. A message that has to rebuild a schema, rewrite policies and redeploy is a message that does more work, and it costs more credits than one that edits a line of client code. The backend you choose decides what "add a field" means to the agent, and therefore what it costs you, on every platform that charges for the agent's effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Skapi is built for AI agents
&lt;/h2&gt;

&lt;p&gt;An agent is at its best when the whole backend is one SDK it can write against, with nothing to provision, migrate or deploy in between. That is what Skapi is: accounts, database, files, CDN, emails, realtime, hosting and webhooks behind one JavaScript library, on a plain HTML page or in any framework, with a free plan to start on.&lt;/p&gt;

&lt;p&gt;Skapi has no schema. A record is a piece of data with a few words attached: the table it belongs to, who may read it, and optionally what it is indexed by. The table exists because a record said so. That is what makes the agent's side so small: there is no relational schema to design before the first record, no policy language to learn, and nothing to deploy, so the design work an agent does is the one word that says who may read the record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tasks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;priority&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole backend side of a task with an attachment. &lt;code&gt;form&lt;/code&gt; is the HTML form the user filled in, file input included, and the file is stored and served from a CDN without another line. &lt;code&gt;access_group: 'private'&lt;/code&gt; is the entire security policy: the record's owner can read it, nobody else can, until the owner grants someone access. Change &lt;code&gt;'private'&lt;/code&gt; to &lt;code&gt;'authorized'&lt;/code&gt; and every signed-in user can read it; change it to &lt;code&gt;'public'&lt;/code&gt; and everyone can.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-records.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-records.webp" alt="The Database page of a Skapi project: records from the tasks, projects, comments, attachments, profiles and settings tables in one list, each with an access icon, an ID, tags and its data" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Database page after the three steps above. Six tables, none of them created by hand. The lock, group and globe icons are the access groups: private, authorized and public.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Open any record and you are looking at the entire data model of a Skapi project: the table name, the access group, one index, a reference, tags, and the data itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-record.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-record.webp" alt="A single task record opened on the Database page: record info, table name and access group, subscription flags, the priority index, options, the JSON data and a Files section" width="800" height="975"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One record. Everything an agent would otherwise put into a migration is a field on this form, and every field is optional except the table name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Accounts work the same way. &lt;code&gt;skapi.signup(event)&lt;/code&gt; on a form creates the user, &lt;code&gt;skapi.login(event)&lt;/code&gt; signs them in, and the confirmation email, the password reset, the welcome mail and the user list on your dashboard are already there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-users.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fvibe-users.webp" alt="The Users page of a Skapi project: five users with email, name and approval status, and toolbar actions to grant access, block, unblock and delete" width="800" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Users page. Signup, login, verification, password reset and the admin actions come with the project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The same project also carries what a growing app asks for next, none of it a separate build: realtime messaging, automated emails and newsletters, secret keys and request forwarding for third-party APIs, tickets that accept webhooks, OpenID login and web hosting. Each one is a method on the SDK or a card on the dashboard, and each one is one loop of the second kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;The cheapest way to check any of this is to build something. A Free project costs nothing.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a project at &lt;a href="https://www.skapi.com" rel="noopener noreferrer"&gt;skapi.com&lt;/a&gt; and copy its Project ID from the Settings page.&lt;/li&gt;
&lt;li&gt;On the same page, download the prompt file for your tool from &lt;strong&gt;For AI Agents&lt;/strong&gt;, and put it in the root of your project.&lt;/li&gt;
&lt;li&gt;Start the agent with the opening prompt: &lt;code&gt;My Skapi project ID is "&amp;lt;Project ID&amp;gt;". Build me a task board with accounts, private tasks and attachments.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ask for the next change. Then the one after that.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first build is an afternoon. Every change after it costs what a change to the client code costs, because that is all it is. Your agent's tokens go into your app, not into the plumbing under it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibecoding</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The real price of a backend in São Paulo: $529 on AWS vs $19 on Skapi</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Fri, 02 Oct 2026 05:52:52 +0000</pubDate>
      <link>https://dev.to/skapi_api/the-real-price-of-a-backend-in-sao-paulo-529-on-aws-vs-19-on-skapi-l3e</link>
      <guid>https://dev.to/skapi_api/the-real-price-of-a-backend-in-sao-paulo-529-on-aws-vs-19-on-skapi-l3e</guid>
      <description>&lt;h2&gt;
  
  
  The real price of a backend in São Paulo: $529 on AWS vs $19 on Skapi
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://www.skapi.com/articles/2026-09-28-00" rel="noopener noreferrer"&gt;the last article&lt;/a&gt; we said a Skapi project in São Paulo runs at a fraction of what a traditional server backend costs there, and that getting there was hard. Here is the bill, line by line: $528.71 a month for the traditional stack, $19 for the same backend on Skapi, and how we made that work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" alt="Skapi" width="244" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why São Paulo is expensive
&lt;/h2&gt;

&lt;p&gt;AWS charges more in São Paulo than in most of its regions, and it is not a rounding error. Here is what the same building blocks cost in São Paulo (sa-east-1) and in N. Virginia (us-east-1), the region most tutorials silently assume you are in. List prices, on demand, as of September 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Building block&lt;/th&gt;
&lt;th&gt;São Paulo&lt;/th&gt;
&lt;th&gt;N. Virginia&lt;/th&gt;
&lt;th&gt;São Paulo costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App server, t3.medium, per hour&lt;/td&gt;
&lt;td&gt;$0.0672&lt;/td&gt;
&lt;td&gt;$0.0416&lt;/td&gt;
&lt;td&gt;62% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database, RDS db.t3.medium Multi-AZ, per hour&lt;/td&gt;
&lt;td&gt;$0.302&lt;/td&gt;
&lt;td&gt;$0.145&lt;/td&gt;
&lt;td&gt;108% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database storage, gp3, per GB-month&lt;/td&gt;
&lt;td&gt;$0.219&lt;/td&gt;
&lt;td&gt;$0.115&lt;/td&gt;
&lt;td&gt;90% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server disk, EBS gp3, per GB-month&lt;/td&gt;
&lt;td&gt;$0.152&lt;/td&gt;
&lt;td&gt;$0.080&lt;/td&gt;
&lt;td&gt;90% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT gateway, per hour&lt;/td&gt;
&lt;td&gt;$0.093&lt;/td&gt;
&lt;td&gt;$0.045&lt;/td&gt;
&lt;td&gt;107% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application load balancer, per hour&lt;/td&gt;
&lt;td&gt;$0.034&lt;/td&gt;
&lt;td&gt;$0.0225&lt;/td&gt;
&lt;td&gt;51% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File storage, S3 Standard, per GB-month&lt;/td&gt;
&lt;td&gt;$0.0405&lt;/td&gt;
&lt;td&gt;$0.023&lt;/td&gt;
&lt;td&gt;76% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data transfer out, per GB&lt;/td&gt;
&lt;td&gt;$0.150&lt;/td&gt;
&lt;td&gt;$0.090&lt;/td&gt;
&lt;td&gt;67% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN delivery, CloudFront, per GB&lt;/td&gt;
&lt;td&gt;$0.110&lt;/td&gt;
&lt;td&gt;$0.085&lt;/td&gt;
&lt;td&gt;29% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN requests, CloudFront HTTPS, per 10,000&lt;/td&gt;
&lt;td&gt;$0.022&lt;/td&gt;
&lt;td&gt;$0.010&lt;/td&gt;
&lt;td&gt;120% more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email, SES, per 1,000 messages&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;the same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User accounts, Cognito Lite, per monthly active user&lt;/td&gt;
&lt;td&gt;$0.0055&lt;/td&gt;
&lt;td&gt;$0.0055&lt;/td&gt;
&lt;td&gt;the same&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;CDN prices follow the viewer, not the origin, so a Brazilian audience pays the South America rate wherever the files live.&lt;/p&gt;

&lt;p&gt;Two things stand out. The parts that run all month, servers, databases and gateways, carry the steepest premium. The parts that only cost when someone uses them, email and identity, cost the same everywhere. Keep that split in mind. It is the whole story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bill for a traditional backend
&lt;/h2&gt;

&lt;p&gt;Let's price a modest production app. Not a toy: 50,000 registered users, 20,000 of whom sign in each month, 3 GB of database, 120 GB of uploaded files, 100 GB served through the CDN each month, and 5,000 transactional emails. That is the exact ceiling of Skapi's Standard plan, so the comparison at the end is fair.&lt;/p&gt;

&lt;p&gt;Built the traditional way in São Paulo, with the redundancy you would want for something real, the bill looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Per month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App servers&lt;/td&gt;
&lt;td&gt;2 × t3.medium, one per availability zone&lt;/td&gt;
&lt;td&gt;$98.11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server disks&lt;/td&gt;
&lt;td&gt;2 × 30 GB gp3, plus daily snapshots&lt;/td&gt;
&lt;td&gt;$13.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancer&lt;/td&gt;
&lt;td&gt;Application Load Balancer, one capacity unit&lt;/td&gt;
&lt;td&gt;$32.85&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT gateway&lt;/td&gt;
&lt;td&gt;Outbound access for servers in private subnets&lt;/td&gt;
&lt;td&gt;$69.75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;RDS PostgreSQL db.t3.medium, Multi-AZ, 20 GB&lt;/td&gt;
&lt;td&gt;$229.22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File storage&lt;/td&gt;
&lt;td&gt;S3, 120 GB and its requests&lt;/td&gt;
&lt;td&gt;$6.68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;CloudFront, 100 GB and 2 million requests&lt;/td&gt;
&lt;td&gt;$15.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API traffic out&lt;/td&gt;
&lt;td&gt;30 GB of responses to the internet&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email&lt;/td&gt;
&lt;td&gt;SES, 5,000 messages&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User accounts&lt;/td&gt;
&lt;td&gt;Cognito Lite, 20,000 active users, first 10,000 free&lt;/td&gt;
&lt;td&gt;$55.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS and monitoring&lt;/td&gt;
&lt;td&gt;Route 53 zone, CloudWatch basics&lt;/td&gt;
&lt;td&gt;$3.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$528.71&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can cut that down. Run one server in a public subnet, drop the load balancer and the NAT gateway, run the database in a single availability zone, and it comes to about $256 a month. It also means the site goes down every time you deploy, reboot, or the database enters its maintenance window, and there is nothing to fail over to when the one server dies.&lt;/p&gt;

&lt;p&gt;For scale: the same production stack in N. Virginia comes to about $311. São Paulo adds 70% to the bill for the privilege of being close to your users.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;List prices, on demand, before discounts. A one-year reservation trims the server and database lines by roughly a third, in exchange for paying for capacity you may never use. AWS's always-free allowances would cover the CDN and transfer lines at this size; they are shown at list price because they are the lines that grow with success.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the bill does not show
&lt;/h2&gt;

&lt;p&gt;The monthly figure is the easy part. Every line in it is also a thing you have to build, and then keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Servers.&lt;/strong&gt; Choose an image, harden it, open the right ports and only those, install a runtime, write a deploy pipeline, put TLS in front of it, and then keep it patched for the rest of its life. Two servers means doing everything twice and keeping them identical. Autoscaling means an image pipeline on top of that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The database.&lt;/strong&gt; A subnet group, a parameter group, a security group, a backup window, a maintenance window, a failover test you actually run, connection pooling so a traffic spike does not exhaust it, and a plan for the day the disk fills up. Multi-AZ doubles the price, and it is the only version you would put a business on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Files and the CDN.&lt;/strong&gt; A bucket with public access blocked, a policy that lets your servers write and nobody else, presigned URLs so users can upload without going through your servers, CORS rules, lifecycle rules. Then a CloudFront distribution with origin access control, cache policies, invalidations, a custom domain, and a certificate that, for CloudFront, must be issued in N. Virginia whatever region your files live in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The network.&lt;/strong&gt; A VPC, public and private subnets in two zones, route tables, an internet gateway, a NAT gateway, and IAM roles narrow enough to be safe and wide enough to work. None of this serves a single request. It is the price of admission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And then the months after.&lt;/strong&gt; Patches, certificate renewals, disk alarms, the 3 a.m. page, the database upgrade you have been putting off. If one developer spends even four hours a month keeping this running, that time alone costs more than the entire Skapi plan below, at any rate you would pay a developer in São Paulo, Buenos Aires or Bogotá.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated emails, compared
&lt;/h2&gt;

&lt;p&gt;Email is the line that looks free and is not. SES charges $0.10 per thousand messages, so 5,000 emails cost fifty cents in São Paulo, exactly as in Virginia. The cost is everything around that number.&lt;/p&gt;

&lt;p&gt;Before SES will send a real email you verify a domain, publish DKIM, SPF and DMARC records, and apply to leave the sandbox with a written description of how you handle bounces and complaints. Then you build that handling: notification topics, a suppression list, retries. Then the actual emails: a verification flow with a code that expires, a welcome email, an invitation, each one a template, a trigger and a queue so a burst of signups does not hit a rate limit. If you want to send a newsletter, add subscriber storage, a subscribe and unsubscribe flow that complies with the law in every country you send to, and a sending pipeline that paces itself.&lt;/p&gt;

&lt;p&gt;On Skapi that whole list is a settings page. Verification, welcome and invitation emails go out from your project's own sending address with no code. Newsletters have subscribers, subscription and unsubscribe links, and a send button. Standard includes 5,000 sends a month and Premium 50,000, and the deliverability plumbing is done once, for everyone, and kept warm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same backend on Skapi
&lt;/h2&gt;

&lt;p&gt;Same users, same data, same traffic, same city:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7q2j9u5n5xmwwfe1s81a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7q2j9u5n5xmwwfe1s81a.png" alt="Backend with Skapi" width="800" height="687"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;28 times less than the production stack. 13 times less than the one that goes down when you deploy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Over a year, that is $6,345 against $228. The difference is not a discount. It is a different way of running a backend.&lt;/p&gt;

&lt;p&gt;Here is what each side gives you for the money, one line at a time. First the cheapest of each: the AWS bare minimum next to Skapi Standard.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AWS, bare minimum&lt;/th&gt;
&lt;th&gt;Skapi Standard&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price per month&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$255.85&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$19&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;RDS PostgreSQL with 2 vCPU and 4 GB of memory, one zone, 20 GB disk: $114.61&lt;/td&gt;
&lt;td&gt;3 GB, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File storage&lt;/td&gt;
&lt;td&gt;S3, 120 GB: $6.68&lt;/td&gt;
&lt;td&gt;120 GB, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web hosting&lt;/td&gt;
&lt;td&gt;Served from your one server, or from a bucket you configure&lt;/td&gt;
&lt;td&gt;Included, with a subdomain and HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN and bandwidth&lt;/td&gt;
&lt;td&gt;CloudFront 100 GB, 2 million requests, 30 GB of API traffic: $19.90&lt;/td&gt;
&lt;td&gt;100 GB, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User accounts&lt;/td&gt;
&lt;td&gt;Cognito Lite, 20,000 active users: $55.00&lt;/td&gt;
&lt;td&gt;50,000 accounts, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenID login&lt;/td&gt;
&lt;td&gt;Set up by you in Cognito&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated emails&lt;/td&gt;
&lt;td&gt;SES, 5,000 messages: $0.50. Templates built by you&lt;/td&gt;
&lt;td&gt;5,000 sends a month, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Newsletters&lt;/td&gt;
&lt;td&gt;Built by you: list, opt-out link, sending&lt;/td&gt;
&lt;td&gt;5,000 subscribers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realtime data&lt;/td&gt;
&lt;td&gt;Built by you, on the one server&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push notifications&lt;/td&gt;
&lt;td&gt;Built by you&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;Endpoints you write and host&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API secret keys&lt;/td&gt;
&lt;td&gt;Kept on your server&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App server&lt;/td&gt;
&lt;td&gt;1 × t3.medium, 30 GB disk with snapshots: $55.66&lt;/td&gt;
&lt;td&gt;None to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring and DNS&lt;/td&gt;
&lt;td&gt;CloudWatch and Route 53: $3.50&lt;/td&gt;
&lt;td&gt;Usage meters in the dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant&lt;/td&gt;
&lt;td&gt;No: one server, one zone&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend deploys&lt;/td&gt;
&lt;td&gt;The site goes down each time&lt;/td&gt;
&lt;td&gt;None needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;A bigger server, by hand&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;A day&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upkeep&lt;/td&gt;
&lt;td&gt;Yours&lt;/td&gt;
&lt;td&gt;Ours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And the full version of each: the AWS production stack next to Skapi Premium. To be generous to AWS, we added database overage to Premium until it matches the 20 GB database disk of the AWS stack, even though Premium already outdoes that stack everywhere else: five times the files, four times the bandwidth, ten times the emails.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;AWS, production&lt;/th&gt;
&lt;th&gt;Skapi Premium&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Price per month&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$528.71&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$94&lt;/strong&gt; ($89 plan, $5 overage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;RDS PostgreSQL with 2 vCPU and 4 GB of memory, two zones, 20 GB disk: $229.22&lt;/td&gt;
&lt;td&gt;20 GB: 10 GB included, 10 GB more at $0.50 per GB, $5.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File storage&lt;/td&gt;
&lt;td&gt;S3, 120 GB: $6.68&lt;/td&gt;
&lt;td&gt;600 GB, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web hosting&lt;/td&gt;
&lt;td&gt;Served from your servers, or from a bucket you configure&lt;/td&gt;
&lt;td&gt;Included, with a subdomain and HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN and bandwidth&lt;/td&gt;
&lt;td&gt;CloudFront 100 GB, 2 million requests, 30 GB of API traffic: $19.90&lt;/td&gt;
&lt;td&gt;400 GB, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User accounts&lt;/td&gt;
&lt;td&gt;Cognito Lite, 20,000 active users: $55.00&lt;/td&gt;
&lt;td&gt;100,000 accounts, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenID login&lt;/td&gt;
&lt;td&gt;Set up by you in Cognito&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated emails&lt;/td&gt;
&lt;td&gt;SES, 5,000 messages: $0.50. Templates built by you&lt;/td&gt;
&lt;td&gt;50,000 sends a month, included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Newsletters&lt;/td&gt;
&lt;td&gt;Built by you: list, opt-out link, sending&lt;/td&gt;
&lt;td&gt;50,000 subscribers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realtime data&lt;/td&gt;
&lt;td&gt;Built by you, across two servers&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push notifications&lt;/td&gt;
&lt;td&gt;Built by you&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhooks&lt;/td&gt;
&lt;td&gt;Endpoints you write and host&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API secret keys&lt;/td&gt;
&lt;td&gt;Kept on your servers&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App servers&lt;/td&gt;
&lt;td&gt;2 × t3.medium, 2 × 30 GB disk with snapshots: $111.31&lt;/td&gt;
&lt;td&gt;None to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancer&lt;/td&gt;
&lt;td&gt;One capacity unit: $32.85&lt;/td&gt;
&lt;td&gt;None to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT gateway&lt;/td&gt;
&lt;td&gt;For the private subnets: $69.75&lt;/td&gt;
&lt;td&gt;None to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring and DNS&lt;/td&gt;
&lt;td&gt;CloudWatch and Route 53: $3.50&lt;/td&gt;
&lt;td&gt;Usage meters in the dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redundant&lt;/td&gt;
&lt;td&gt;Yes: two zones, set up and tested by you&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend deploys&lt;/td&gt;
&lt;td&gt;Rolling, through a pipeline you build&lt;/td&gt;
&lt;td&gt;None needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scaling&lt;/td&gt;
&lt;td&gt;Rules you write and tune&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Days&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upkeep&lt;/td&gt;
&lt;td&gt;Yours&lt;/td&gt;
&lt;td&gt;Ours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is $94 against $528.71. Even with the overage counted, Premium costs more than 5 times less.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The prices in each AWS column add up to the total at the top of it. The lines without a price are the ones the bill leaves out: they are paid in time instead. Both AWS columns are the bills from earlier in this article, sized for 120 GB of files and 20,000 active users. Sized for what Premium includes (600 GB of files, 400 GB of bandwidth, 50,000 emails, 40,000 active users) on the same servers, the AWS production bill comes to about $728 a month.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Standard is $19 a month in São Paulo, the same as in Oregon, Paris, Mumbai, Singapore or Seoul. For that you get the database, file storage, CDN, web hosting, user accounts with OpenID login, automated emails and newsletters, realtime data, API secret keys and webhooks, with room for 50,000 user accounts, 3 GB of database, 120 GB of files and 100 GB of bandwidth. Premium is $89 for 100,000 accounts, 10 GB of database, 600 GB of files and 400 GB of bandwidth. A small project runs on Free for nothing at all.&lt;/p&gt;

&lt;p&gt;And all of that is before counting a single hour of anyone's time. The four hours a month from the section above cost more than the Skapi plan on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we got the price down
&lt;/h2&gt;

&lt;p&gt;We are not going to draw you our architecture, and we do not have secret hardware. What we have is a set of rules we did not break, and in São Paulo they matter more than anywhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing idles.&lt;/strong&gt; Look back at the first table. The steep premiums all sit on things billed by the hour whether or not anyone is using them. A traditional backend in São Paulo pays for 730 hours of servers, 730 hours of database and 730 hours of gateway every month, at 3 a.m. on a Sunday as much as at noon on payday. Every part of Skapi is metered by use: a request, a record read, a gigabyte stored, a message sent. When your users are asleep, so is your bill. That is what serverless means to us, and we applied it to every resource in the chain, not just the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed costs are shared, not repeated.&lt;/strong&gt; The expensive lines above, the redundant database, the load balancer, the gateway, are fixed costs a single project pays alone. In Skapi those costs exist once per region, and every project in that region shares them. Your project's share of a fixed cost is close to zero, and that, more than any clever trick, is where the fraction comes from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bytes go to the cheapest place that can hold them.&lt;/strong&gt; Records live in a database that charges for what you store and read, not for a machine that waits. Anything large, files, uploads, hosted sites, record payloads over 32 KB, goes straight to object storage and is served from the CDN, so a download never touches a compute layer at all. Database storage is the most expensive byte in São Paulo, so we keep as few of them there as we can, and &lt;a href="https://dev.to/articles/2026-09-16"&gt;we made that change on every plan&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity and email are services, not servers.&lt;/strong&gt; User accounts live in a managed directory built for that job, with verification, password reset and multi-factor flows already there. Email is a queue, with delivery, bounces, complaints and sender reputation handled once for the whole platform. These are the parts that cost the same in every region, and they are the parts nobody should rebuild per project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale is a consequence, not a plan.&lt;/strong&gt; Because every part is metered, a spike in traffic is a larger bill for that hour, not an outage, and a quiet month is a smaller one. There is no instance size to pick, no capacity to reserve, no failover to rehearse. The pieces that would go down are the pieces we do not run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security comes with the shape.&lt;/strong&gt; There are no servers in your project to patch, no SSH, no open ports. Every request is authenticated and authorized on its own, every component talks to the next with the narrowest permission that works, data is encrypted in transit and at rest, and private records can be &lt;a href="https://dev.to/articles/2026-09-07-02"&gt;encrypted in the browser&lt;/a&gt; under your user's own password. A project's data never leaves the region it was created in. For Latin American teams, that last line often matters as much as the price.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why São Paulo was hard
&lt;/h2&gt;

&lt;p&gt;Here is the part we promised to explain. The rules above are how Skapi works everywhere. São Paulo tested every one of them.&lt;/p&gt;

&lt;p&gt;Serverless does not escape the regional premium. It meets it on a different bill. Every meter we rely on runs faster in São Paulo: storage per gigabyte, requests per million, bytes out per gigabyte. Some managed services cost more there, some arrived later than in other regions, and a few of the shared pieces that make a fixed cost disappear had to be planned again so they would still disappear at São Paulo prices. Keeping the plan price flat, $19 in São Paulo exactly as in Oregon, meant wasting nothing: fewer hops per request, caching wherever it pays for itself, moving bytes as little as possible, and checking that no component, anywhere in the chain, sits idle on your behalf.&lt;/p&gt;

&lt;p&gt;It was the hardest region we have opened, and we opened it on purpose. We want developers in Brazil and across Latin America to have affordable, efficient, solid access to backend infrastructure in their own region, at the same price as everyone else, without the tax the region has quietly charged for years. A student in Recife and a startup in Santiago should be able to run something real for the price of a few coffees a month. Now they can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;Create a project, choose São Paulo, and run the numbers against your own AWS bill. If the difference is not obvious in the first month, we would like to hear about it.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Olá, São Paulo: Skapi now runs in São Paulo, its first South American region</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Wed, 30 Sep 2026 10:30:40 +0000</pubDate>
      <link>https://dev.to/skapi_api/ola-sao-paulo-skapi-now-runs-in-sao-paulo-its-first-south-american-region-42g7</link>
      <guid>https://dev.to/skapi_api/ola-sao-paulo-skapi-now-runs-in-sao-paulo-its-first-south-american-region-42g7</guid>
      <description>&lt;h2&gt;
  
  
  Olá, São Paulo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2FFlag_Brazil.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2FFlag_Brazil.png" alt="Flag of Brazil" width="500" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skapi now runs in Brazil. São Paulo is our newest region, and it is the first place in South America where a project's database, its files and its user accounts all live on the same continent as the people using them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Fimg%2Fskapi-logo-cut-small.png" alt="Skapi" width="244" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Olá, Brasil! Olá, América Latina! A partir de hoje, a Skapi roda em São Paulo. Se você cria aplicações para usuários no Brasil ou em qualquer canto da América Latina, eles não precisam mais cruzar meio mundo a cada requisição: o banco de dados, os arquivos e as contas do seu projeto ficam pertinho, e cada login, cada consulta e cada upload responde bem mais rápido. Um abraço, e bom código!&lt;/p&gt;

&lt;h2&gt;
  
  
  The first South American region
&lt;/h2&gt;

&lt;p&gt;Until now, the closest Skapi region to a user in Rio de Janeiro was in Paris, 9,167 km away on the other side of the Atlantic. A user in Lima was sent to Oregon, 7,720 km to the north. Every login, every query, every file upload made that journey and came back. It worked, and it worked well, but physics always took its cut.&lt;/p&gt;

&lt;p&gt;São Paulo changes the shape of that map. Here is what the same requests look like now:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your user is in&lt;/th&gt;
&lt;th&gt;To São Paulo&lt;/th&gt;
&lt;th&gt;Nearest region before&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rio de Janeiro&lt;/td&gt;
&lt;td&gt;361 km&lt;/td&gt;
&lt;td&gt;9,167 km (Paris)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Belo Horizonte&lt;/td&gt;
&lt;td&gt;491 km&lt;/td&gt;
&lt;td&gt;8,913 km (Paris)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Porto Alegre&lt;/td&gt;
&lt;td&gt;852 km&lt;/td&gt;
&lt;td&gt;10,254 km (Paris)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Brasília&lt;/td&gt;
&lt;td&gt;872 km&lt;/td&gt;
&lt;td&gt;8,725 km (Paris)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buenos Aires&lt;/td&gt;
&lt;td&gt;1,675 km&lt;/td&gt;
&lt;td&gt;10,852 km (Oregon)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Santiago&lt;/td&gt;
&lt;td&gt;2,585 km&lt;/td&gt;
&lt;td&gt;10,099 km (Oregon)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lima&lt;/td&gt;
&lt;td&gt;3,454 km&lt;/td&gt;
&lt;td&gt;7,720 km (Oregon)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bogotá&lt;/td&gt;
&lt;td&gt;4,328 km&lt;/td&gt;
&lt;td&gt;6,339 km (Oregon)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A user in Rio de Janeiro is now twenty-five times closer to their data. A user in Buenos Aires, six times closer. That difference is not a benchmark you have to squint at. It is the feeling of a page that loads before the spinner has a reason to appear, a form that saves the instant it is submitted, and a file that starts downloading rather than thinking about it first.&lt;/p&gt;

&lt;p&gt;From Colombia and Venezuela south, São Paulo is now the nearest region by a wide margin. Users in Mexico and most of Central America are still closest to Oregon, and Skapi keeps offering Oregon first there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Skapi runs
&lt;/h2&gt;

&lt;p&gt;Six regions across North America, South America, Europe and Asia, placed so that most of the connected world has a Skapi region within a short hop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flhd1rbicahq7wp48psg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flhd1rbicahq7wp48psg1.png" alt="Skapi covers 6 regions including LATAM" width="800" height="319"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every dot is a full Skapi region running the complete stack.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Region&lt;/th&gt;
&lt;th&gt;Where it is&lt;/th&gt;
&lt;th&gt;Project ID starts with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;us-west-2&lt;/td&gt;
&lt;td&gt;Oregon, United States&lt;/td&gt;
&lt;td&gt;&lt;code&gt;us72&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sa-east-1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;São Paulo, Brazil&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sa31&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;eu-west-3&lt;/td&gt;
&lt;td&gt;Paris, France&lt;/td&gt;
&lt;td&gt;&lt;code&gt;eu73&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ap-south-1&lt;/td&gt;
&lt;td&gt;Mumbai, India&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ap51&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ap-southeast-1&lt;/td&gt;
&lt;td&gt;Singapore&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ap41&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ap-northeast-2&lt;/td&gt;
&lt;td&gt;Seoul, South Korea&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ap22&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that list from west to east and the spacing is deliberate. Oregon anchors North America, and São Paulo now does the same for South America. Paris covers Europe, North Africa and the Middle East. Mumbai, Singapore and Seoul carry the largest concentration of internet users on the planet between them.&lt;/p&gt;

&lt;p&gt;Every one of those regions runs the same Skapi. The same SDK call, the same API, the same authentication, the same record model. There is no South American edition with fewer features and no waiting for a region to catch up. A region is either serving your project completely or it is not serving it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now you choose where your project lives
&lt;/h2&gt;

&lt;p&gt;When you create a project, you pick the region.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fcreate-project-region.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fcdn.broadwayinc.com%2Farticles%2Fimg%2Fcreate-project-region.webp" alt="The Create a new project dialog: a project name, an optional description, and a Region field reading Sao Paulo, Brazil (closest to you), with a note that the project's data is stored in the region you pick and cannot be moved later" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The new-project dialog. The closest region is offered first, every region is in the list, and the note under it says the one thing to know before you continue.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Skapi offers you the closest one first, worked out from where you are, so the fast choice is also the default choice. But the list is right there, and the right answer is often not your own city. If you are a studio in Lisbon with an audience in Rio, put the project in São Paulo. If you are three founders in São Paulo selling to Madrid, put it in Paris. Your users are the ones making the round trip thousands of times a day. You make it twice.&lt;/p&gt;

&lt;p&gt;Pick deliberately, because a project stays where you put it. The region is baked into the first four characters of the project ID, which is why a São Paulo project announces itself as &lt;code&gt;sa31...&lt;/code&gt; for the rest of its life. It is a permanent decision in the same way a good foundation is permanent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data that stays where you put it
&lt;/h2&gt;

&lt;p&gt;There is a second reason to care about a region. A project created in São Paulo keeps its database records, its uploaded files, its hosted site and its user accounts in Brazil. Not routed through it. Stored in it.&lt;/p&gt;

&lt;p&gt;For teams whose customers ask where their data is kept, and for anyone working under the LGPD, Brazil's data protection law, that single sentence is usually the one that unblocks the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why São Paulo means so much to us
&lt;/h2&gt;

&lt;p&gt;A new region is always satisfying to open, but this one means more to us than most, for two reasons.&lt;/p&gt;

&lt;p&gt;The first is reach. South America is home to hundreds of millions of people who live on their phones, and until now every one of them was a long way from any Skapi project. That distance showed up in your business as slower pages, abandoned forms and users who quietly gave up. With São Paulo, your product can reach them properly. It loads faster, it saves faster, and it feels like it was built for them, because now it runs right next to them.&lt;/p&gt;

&lt;p&gt;The second is cost. Running a traditional server backend on AWS in South America is expensive, noticeably more so than the same servers in North America or Europe, and that cost alone has kept many teams from serving the region well, or at all. We made sure a Skapi project in São Paulo runs at a fraction of that price. Making that possible was a real challenge for us, and it took a lot more than flipping a switch. That story deserves its own space, so we will cover it in the next article.&lt;/p&gt;

&lt;p&gt;So: create a project, choose São Paulo, and let your Brazilian and Latin American users feel what their applications were capable of all along.&lt;/p&gt;

&lt;p&gt;Bem-vindos a bordo. Bom desenvolvimento, e até breve.&lt;/p&gt;

</description>
      <category>security</category>
      <category>database</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Skapi 2.0 Is Out: Private Data Encryption, API Streaming, and Safer Backend Requests</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Mon, 07 Sep 2026 09:54:02 +0000</pubDate>
      <link>https://dev.to/skapi_api/skapi-20-is-out-private-data-encryption-api-streaming-and-safer-backend-requests-156b</link>
      <guid>https://dev.to/skapi_api/skapi-20-is-out-private-data-encryption-api-streaming-and-safer-backend-requests-156b</guid>
      <description>&lt;h2&gt;
  
  
  Skapi 2.0 is out
&lt;/h2&gt;

&lt;p&gt;The stable tag has moved: &lt;code&gt;npm i skapi-js&lt;/code&gt; installs 2.0 today. Private records can now be encrypted in the browser, under your user's own password.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.bunnyquery.com%2Fskapi-square-128.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fwww.bunnyquery.com%2Fskapi-square-128.webp" alt="Skapi" width="128" height="128"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i skapi-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In August that command still installed 1.8.3, and we said nothing you were running would change until you asked for it. This is you asking for it. If you are not ready, &lt;code&gt;npm i skapi-js@1.8.3&lt;/code&gt; keeps you exactly where you are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bunnyquery.com/articles/2026-08-12" rel="noopener noreferrer"&gt;The release candidate post&lt;/a&gt; covered the naming, the polling queues and the session storage work. This one is about what landed on top of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Private records can be encrypted in the browser now
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;new Skapi("&amp;lt;Project ID&amp;gt;", { encryption: true })&lt;/code&gt; encrypts the &lt;code&gt;data&lt;/code&gt; of every record you write to &lt;code&gt;access_group: 'private'&lt;/code&gt;, and the contents of the files attached to it, before any of it leaves the page. The key is derived from the user's own password and never leaves their device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Skapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;Project ID&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;encryption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;minPasswordLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;diagnosis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confidential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The calls do not change. &lt;code&gt;postRecord()&lt;/code&gt; seals, &lt;code&gt;getRecords()&lt;/code&gt; opens, and there is no key for your end user to manage. Sharing is the call it always was: &lt;code&gt;grantPrivateRecordAccess()&lt;/code&gt; wraps that record's data key to the recipient's published public key, so the owner does not have to be online when the grantee reads, and &lt;code&gt;removePrivateRecordAccess()&lt;/code&gt; rolls the data key, which makes revocation forward only.&lt;/p&gt;

&lt;p&gt;Skapi has always decided who may read a record, on the server, against a signed identity the browser cannot forge. What is new is that the bytes stop being readable by the people running the database. In the words our documentation uses: a provider who dumps the database, the storage bucket and every request log cannot recover that record's &lt;code&gt;data&lt;/code&gt; without a successful offline guess against the user's password.&lt;/p&gt;

&lt;h2&gt;
  
  
  An API response can render while it is still being written
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;clientSecretRequest()&lt;/code&gt; now takes &lt;code&gt;stream: true&lt;/code&gt; and &lt;code&gt;onStream(chunk, seq, via)&lt;/code&gt;. The server reads the destination's response incrementally and relays the raw bytes as they arrive, so your page renders an answer while the destination is still writing it. Add &lt;code&gt;realtime: true&lt;/code&gt; and each piece also comes over skapi's websocket the moment it is relayed, instead of on the next poll tick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientSecretRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;clientSecretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.openai.com/v1/chat/completions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer $CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// the destination's own flag&lt;/span&gt;
    &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// skapi's flag: relay the answer as it arrives&lt;/span&gt;
    &lt;span class="na"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;poll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;via&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;readEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your key still never reaches the browser: &lt;code&gt;$CLIENT_SECRET&lt;/code&gt; is filled in on the server as the call goes out. Skapi parses nothing, so the framing and the buffering of a chunk that ended mid frame are yours, which is why this works against any destination on day one rather than only the ones we know about. The websocket is an accelerator and never the source of truth: a dropped socket is not an error, and the read continues at poll speed.&lt;/p&gt;

&lt;p&gt;One trap. &lt;code&gt;stream&lt;/code&gt; says how skapi reads the response; asking the destination to produce one incrementally is a separate flag in your own request body. Skapi inspects neither, so setting only one of the two goes wrong quietly, with no error from either side.&lt;/p&gt;

&lt;h2&gt;
  
  
  forwardRequest is the method we promised and never named
&lt;/h2&gt;

&lt;p&gt;The release candidate post said 2.0 had one genuinely new method, then no section ever said which. It is &lt;code&gt;forwardRequest()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forwardRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How did last quarter go?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.yourbackend.com/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;onStream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It relays a request to your own backend from skapi's servers and streams the response back, with your project's API key attached server side where the browser cannot read it. Your backend also receives &lt;code&gt;x-skapi-user&lt;/code&gt;, taken from the verified session, and a request that tries to set an &lt;code&gt;x-skapi-&lt;/code&gt; header itself is rejected, so that attribution is safe to trust. The first argument can be a submit event, a form element, a &lt;code&gt;FormData&lt;/code&gt; or a plain object, and a form goes as &lt;code&gt;multipart/form-data&lt;/code&gt; with its files.&lt;/p&gt;

&lt;p&gt;Your backend has to resolve to a public address, so private ranges, loopback and cloud metadata addresses are refused. The destination instructions ride in a header capped at 4096 characters measured after escaping, where each non-ASCII character costs six, so put anything long in the body. And there is no re-attach call for this one: the response is yours once, as it arrives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things to know before you upgrade
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your &lt;code&gt;data&lt;/code&gt; is stored as JSON text, and the SDK is the only decoder.&lt;/strong&gt; The API encodes it on every write regardless of which client wrote it, so an older SDK reading one of these records gets the raw encoded string instead of your object, quietly, with no error. The test is one line: a &lt;code&gt;data&lt;/code&gt; you expected as an object that reads back as a string beginning &lt;code&gt;!J%&lt;/code&gt; is this. If a project has more than one client on it, a mobile build, a Node worker, a REST caller, upgrade them together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two changes ship with the API rather than the SDK, so they are already true for you.&lt;/strong&gt; Moving a record into or out of &lt;code&gt;'private'&lt;/code&gt; clears every private access grant on it, encryption or not, so grant those users again after the move. And only a record's owner may cross that line: a master account still reads, edits and deletes anything in its project, but it can no longer move somebody else's record between &lt;code&gt;'private'&lt;/code&gt; and a numbered group, because with encryption in play that would destroy the record while reporting success.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four small changes arrive whether you ask or not.&lt;/strong&gt; &lt;code&gt;skapi.util.decodeServiceId()&lt;/code&gt; is &lt;code&gt;decodeProjectId()&lt;/code&gt;, with no alias. &lt;code&gt;table: 'name'&lt;/code&gt; on an update no longer moves that record to the public access group, so pass &lt;code&gt;table: { name: 'name', access_group: 0 }&lt;/code&gt; if you wanted the move. &lt;code&gt;deleteRecords()&lt;/code&gt; returns normalized records, so read &lt;code&gt;record_id&lt;/code&gt; and &lt;code&gt;table.name&lt;/code&gt; rather than the old short keys. And &lt;code&gt;data: null&lt;/code&gt; stores a null instead of removing the field.&lt;/p&gt;

&lt;p&gt;Everything else waits to be asked for, including the new &lt;code&gt;require_login&lt;/code&gt; project setting, read from &lt;code&gt;getConnectionInfo()&lt;/code&gt; as &lt;code&gt;conf.require_login&lt;/code&gt;. It makes the SDK refuse record reads from a signed out session: a guard rail against a page leaking your public records by accident, not an access control, since the API still serves &lt;code&gt;access_group: 0&lt;/code&gt; to anyone who asks.&lt;/p&gt;

&lt;p&gt;One correction while you have &lt;a href="https://docs.skapi.com/versionlog/versions.html" rel="noopener noreferrer"&gt;the changelog&lt;/a&gt; open. &lt;code&gt;condition: '&amp;lt;='&lt;/code&gt; on a string index value as an "ends with" search is a 1.8.0 change, not a 2.0 one, and the API serves it, so it already works from whatever version you are running today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs we could not have found on our own
&lt;/h2&gt;

&lt;p&gt;None of the fixes in that data section came from reading our own code. A string containing &lt;code&gt;*add&lt;/code&gt; destroying a record is not something a test suite finds: somebody has to be storing a sentence a real person wrote, in a real app, before the storage format ever gets the chance to misread it.&lt;/p&gt;

&lt;p&gt;So thank you to everyone who put a project that mattered to them on a release candidate, kept &lt;code&gt;skapi-js@rc&lt;/code&gt; installed while it settled, and wrote in with the boring exact detail: the record id, the payload, the call that came back wrong. That is the part of 2.0 we could not have written ourselves.&lt;/p&gt;

&lt;p&gt;Encryption is the first place where Skapi holds something it genuinely cannot read, and widening that is where the next stretch goes. &lt;code&gt;data&lt;/code&gt; is sealed and &lt;code&gt;index.value&lt;/code&gt; is not, because a range read over ordered keys is what keeps a schemaless query cheap. Protecting what you query on without losing the ability to query it is a hard question, and we are not going to answer it badly.&lt;/p&gt;

&lt;p&gt;There is one more thing your backend does not have now. A copy of your users' data it can read.&lt;/p&gt;

&lt;p&gt;Go build with Skapi, and let your users keep their own keys.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>productivity</category>
      <category>api</category>
      <category>backend</category>
    </item>
    <item>
      <title>The Fastest Way to Deploy a Site in 2026</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Thu, 11 Dec 2025 08:01:39 +0000</pubDate>
      <link>https://dev.to/skapi_api/the-fastest-way-to-deploy-a-site-in-2026-a10</link>
      <guid>https://dev.to/skapi_api/the-fastest-way-to-deploy-a-site-in-2026-a10</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Most developers don’t need a backend. They need a fast way to launch.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the end of 2025, the question many developers ask is no longer how to design a website or structure a backend. The question has become much simpler and far more urgent: &lt;strong&gt;What is the fastest way to deploy a site?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Time to launch has become a key competitive factor for indie developers, small teams, and even established engineers who want to move faster without getting caught in configuration loops and infrastructure overhead.&lt;/p&gt;

&lt;p&gt;This article explores that question through the lens of Alex Lee, an early-career developer who has already grown tired of unnecessary complexity, juggling tools, and backend setup. His experience reveals a shift happening in the industry, and why platforms like &lt;a href="//www.skapi.com"&gt;Skapi&lt;/a&gt; are quickly becoming the fastest and most reliable way to deploy a site with zero configuration and no backend work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Development Pain Point Alex Can’t Ignore:
&lt;/h2&gt;

&lt;p&gt;Alex Lee, a developer with two years of experience and a full-time job, decided to work on side projects he genuinely enjoyed. He was excited to get started, but quickly ran into problems he never expected.&lt;/p&gt;

&lt;p&gt;His idea was clear, the frontend code was ready, and the user flow was straightforward. He wasn’t building a large-scale SaaS platform or a complex distributed application. He just wanted to deploy a clean, static site – a ticket pre-sale platform for his favorite music band – with a few simple functional components: email handling, a form submission endpoint, and some basic state storage.&lt;/p&gt;

&lt;p&gt;Sounds simple, right? Looks even simpler:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih73wk2wrd9e57qkpqu8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih73wk2wrd9e57qkpqu8.png" alt="music group pre-sale platform website" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yet deploying that site turned into a cycle of dashboards, configs, environment variables, SSL settings, file structures, and DNS delays. What should have taken minutes routinely took hours. For an early-career developer, the complexity felt disproportionate to the goal.&lt;/p&gt;

&lt;p&gt;This frustration is increasingly common, and it reveals the core of a major trend: &lt;strong&gt;developers want the fastest possible way to deploy a site without touching backend infrastructure at all&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Search for the Fastest Way to Deploy a Site
&lt;/h2&gt;

&lt;p&gt;In exploring options, Alex tried several of the popular hosting and deployment tools that have defined the industry over the past few years. Each one promised simplicity. Each one advertised speed. Yet most of them came with underlying complexity that slowed down the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;build configurations&lt;/li&gt;
&lt;li&gt;routing files&lt;/li&gt;
&lt;li&gt;backend functions that required separation&lt;/li&gt;
&lt;li&gt;authentication that needed setup&lt;/li&gt;
&lt;li&gt;email APIs requiring external providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Too much for a developer whose project did not require this level of machinery.&lt;/p&gt;

&lt;p&gt;Alex realized the actual question was not &lt;em&gt;What hosting tool should I use?&lt;/em&gt; but rather:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does deploying a small, functional site still require thinking about backend infrastructure at all?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And this is where Alex found &lt;strong&gt;Skapi&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A New Approach: Zero-Backend Deployment
&lt;/h2&gt;

&lt;p&gt;Skapi stood out immediately because it answered the one question that mattered:&lt;br&gt;
&lt;strong&gt;How fast can a developer deploy a site without touching backend configuration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Alex discovered that Skapi’s architecture does something different from traditional hosting platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It hosts static sites instantly through a simple file-drop interface. index.html becomes your root entry point. Files are served instantly through global CDN.&lt;/li&gt;
&lt;li&gt;It removes the backend setup entirely.&lt;/li&gt;
&lt;li&gt;It provides automatic email handling and newsletter flows.&lt;/li&gt;
&lt;li&gt;It supports authentication without configuration.&lt;/li&gt;
&lt;li&gt;It includes built-in storage and database-like record handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77dcx6m1dqjfx5smhjlq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77dcx6m1dqjfx5smhjlq.jpg" alt="easy no-setup backend" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In other words, Skapi collapses multiple layers of development work into a single environment that requires no manual setup. For a developer trying to deploy a side-project, it represented exactly what the industry has been moving toward: &lt;strong&gt;the fastest way to deploy a site with AI-assisted backend logic and zero configuration.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Skapi let me deploy my project without touching any infrastructure at all.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Deploying the Side-Project: A Faster Workflow
&lt;/h2&gt;

&lt;p&gt;Alex’s deployment process shifted dramatically once he tried Skapi. &lt;/p&gt;

&lt;p&gt;The steps became straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prepare the frontend folder.&lt;/li&gt;
&lt;li&gt;Drag files into Skapi’s File Hosting.&lt;/li&gt;
&lt;li&gt;The site is live instantly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk8g325injgm7xqwi0nm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwk8g325injgm7xqwi0nm.jpg" alt="instant web hosting in 2 minutes" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There were no DNS tasks, no build steps, and no backend endpoints to configure. Alex was able to add a form submission flow with Skapi’s built-in API handling. He integrated email automation without any third-party providers. He added basic data storage without provisioning a database.&lt;/p&gt;

&lt;p&gt;This workflow matched exactly what many developers search for. When someone types &lt;strong&gt;the fastest way to deploy a site&lt;/strong&gt; or &lt;strong&gt;how to deploy a website quickly&lt;/strong&gt;, they’re not looking for server diagrams or configuration tutorials. They want a process that lets them build and deploy without obstacles.&lt;/p&gt;

&lt;p&gt;Skapi delivered that experience by treating backend complexity as optional rather than required.&lt;/p&gt;

&lt;p&gt;The industry is changing. Backends will always matter for large-scale systems, but most modern projects: side-project apps, landing pages, prototypes, microtools, and personal products do not require full backend environments. &lt;/p&gt;

&lt;p&gt;This gap between actual requirements and traditional infrastructure has opened the door for tools like Skapi to reshape deployment workflows entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Happy Alex’s Conclusion
&lt;/h2&gt;

&lt;p&gt;Alex’s search for the fastest way to deploy a site ended not with a new tool to configure, but with a different approach to development itself. Skapi provided a clear path where the friction of backend setup disappeared, leaving space for building, refining, and launching a real side-project in a fraction of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fastest way to deploy a site in 2026 is to use tools that eliminate backend work entirely.&lt;/strong&gt; For anyone searching for the quickest and most effective deployment workflow, the answer is becoming clearer each year: choose the tools that let you build without barriers. &lt;/p&gt;

&lt;p&gt;In that landscape, Skapi is quickly emerging as one of the strongest options.&lt;/p&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>automation</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Vibe coding ultimate beginner tutorial 2025</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Fri, 28 Nov 2025 09:03:07 +0000</pubDate>
      <link>https://dev.to/skapi_api/vibe-coding-ultimate-beginner-tutorial-2025-461o</link>
      <guid>https://dev.to/skapi_api/vibe-coding-ultimate-beginner-tutorial-2025-461o</guid>
      <description>&lt;p&gt;We’ve binged a ton of YouTube videos about vibe coding so you don’t have to. Here’s the basic workflow most vibe coders use, but if you don’t care about all the theory and just want an easy way to get a beautiful, working design, keep reading. We’ll give you an ultimate guide using only two tools to make your project good, fast, and free.&lt;/p&gt;

&lt;p&gt;This is what tech bloggers usually do to build a “vibe-coded” website:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hunt for references on Pinterest.&lt;/li&gt;
&lt;li&gt;Ask an AI to generate unique images based on those references.&lt;/li&gt;
&lt;li&gt;If they want extra flair, they add animations and interactivity in AI tools.&lt;/li&gt;
&lt;li&gt;In a website builder, they describe the idea and build it step by step. Right now, Lovable and Bolt are the big names. If you already know some coding and don’t mind paying a subscription, a lot of people go with Claude Code.&lt;/li&gt;
&lt;li&gt;Build backend logic and webhooks without traditional programming. n8n is the trendy choice for that right now.&lt;/li&gt;
&lt;li&gt;Connect the website to a custom domain and publish it using a hosting service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sounds like a lot, right? Not very “vibe.” And imagine how much you end up paying for all those tools and subscriptions.&lt;/p&gt;

&lt;p&gt;We’re going to save your time and your wallet, and show you how to do it in a short, fast, ultimate way using only AI and one smart tool (Skapi - as you’ve probably guessed already).&lt;/p&gt;

&lt;p&gt;Our project concept is an online pre-sale platform for a concert, where people can sign up and join a waiting list to be the first to know when tickets go on sale, and get the earliest chance to buy them.&lt;/p&gt;

&lt;p&gt;Even though we’re an IT company and know what we’re doing, for the sake of the experiment this project was built by our marketing manager, not a developer. If a marketing girl can do it, you can absolutely do it too.&lt;br&gt;
So if you’re ready - let’s get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for references
&lt;/h2&gt;

&lt;p&gt;First things first - we want a pretty, stylish website. But let’s be honest, we’re not designers, right? So we head over to Pinterest and search for “band website”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqut5k8jwa1f2ko77trwt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqut5k8jwa1f2ko77trwt.png" alt="band website pinterest reference" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I literally grabbed the first one, because we don’t need any special functionality from the design. The AI will take care of that for us. And honestly, I chose this one because it’s Joy Division. Enough said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend in a minute
&lt;/h2&gt;

&lt;p&gt;Next, let’s go to &lt;a href="//www.skapi.com"&gt;Skapi&lt;/a&gt;. Create an account and start a free trial service. If you need help, you can find the full walkthrough &lt;a href="https://dev.to/skapi_api/no-more-backend-setup-launch-your-app-with-skapi-in-minutes-b85"&gt;here&lt;/a&gt;, but honestly, it’s straightforward even without instructions.&lt;/p&gt;

&lt;p&gt;After clicking “Create,” you’ll land on your service page. That’s where you’ll see your backend credentials. &lt;/p&gt;

&lt;p&gt;Download SKAPI.md file and copy the prompt. It already contains your credentials.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F904z6ughf3tt26rxokj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F904z6ughf3tt26rxokj4.png" alt="skapi credentials easy backend" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give the prompt to AI
&lt;/h2&gt;

&lt;p&gt;Now let’s jump into Lovable. That’s our tool of choice for this project, because it lets us visually tweak sections of the site and then download the generated code.&lt;/p&gt;

&lt;p&gt;Here’s the prompt I’m using, and when sending it, don’t forget to attach SKAPI.md file and the reference picture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the file "SKAPI.md" as a system prompt. &lt;br&gt;
My Skapi service ID is: [YOUR CREDENTIALS HERE].&lt;br&gt;
Build me a website for Joy Division. A PNG reference is attached.&lt;br&gt;
Adapt it into an online pre-sale platform for a concert tour, where users can subscribe to a newsletter and join a waiting list to be the first to know when tickets go on sale and get early access to buy them. Send users a confirmation email using Skapi’s newsletter feature.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl5n10eq2uimr9l8fek8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl5n10eq2uimr9l8fek8.png" alt="AI website generator" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the functionality Lovable generated from my prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Newsletter subscription via Skapi — visitors can enter their email and instantly join the waitlist&lt;/li&gt;
&lt;li&gt;Email confirmation flow — users receive a confirmation email and must verify their address&lt;/li&gt;
&lt;li&gt;Success handling — automatic redirect after successful verification&lt;/li&gt;
&lt;li&gt;Tour information — clean layout with stats and pre-sale details&lt;/li&gt;
&lt;li&gt;Fully responsive design&lt;/li&gt;
&lt;li&gt;Skapi initialized in index.html using my service ID &lt;/li&gt;
&lt;li&gt;Uses subscribeNewsletter() with group: 'public' for public newsletter subscriptions&lt;/li&gt;
&lt;li&gt;Automatic confirmation email sent through Skapi&lt;/li&gt;
&lt;li&gt;Redirect URL for after the user verifies their email&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing ready website
&lt;/h2&gt;

&lt;p&gt;Now let’s test it. We can do everything directly from Lovable’s preview.&lt;br&gt;
And yes, it works. I entered my email and immediately received the verification link.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhspeofiopelgw56fjikv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhspeofiopelgw56fjikv.png" alt="testing ready website" width="800" height="402"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtgn2oacigxrnx87nh1u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhtgn2oacigxrnx87nh1u.png" alt="email confirmation" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customize your email&lt;/strong&gt;&lt;br&gt;
You can customize all automated emails directly in your Skapi My Services Dashboard.&lt;/p&gt;

&lt;p&gt;Here’s a quick DIY walkthrough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Automated Emails section.&lt;/li&gt;
&lt;li&gt;Create a new entry, give it a name, and register it.&lt;/li&gt;
&lt;li&gt;Copy the generated email address (the one highlighted in red).&lt;/li&gt;
&lt;li&gt;Open your regular email provider - Gmail, Outlook, whatever you use.&lt;/li&gt;
&lt;li&gt;Write the email you want your users to receive and send it to that copied address.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That email becomes your template for the signup confirmation message. From now on, users will receive your version instead of the default one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbzr1qem5wb9a98wy0et.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frbzr1qem5wb9a98wy0et.png" alt="automated emails" width="800" height="357"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qoc2e46sp83g9qrkwqu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qoc2e46sp83g9qrkwqu.png" alt="automated email send" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy your website
&lt;/h2&gt;

&lt;p&gt;Now, back to our Joy Division website.&lt;/p&gt;

&lt;p&gt;The final step is deployment — and here’s the good news:&lt;br&gt;
You can handle everything right inside Skapi, without hunting for third-party hosting or paying extra.&lt;/p&gt;

&lt;p&gt;Here’s how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to Skapi and open File Hosting from the left dashboard.&lt;/li&gt;
&lt;li&gt;Create a new domain by choosing a name and clicking Register.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapjr48z2joxw6y90xeh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fapjr48z2joxw6y90xeh4.png" alt="skapi file hosting feature" width="800" height="349"&gt;&lt;/a&gt;&lt;br&gt;
Next, head over to Lovable:&lt;/p&gt;

&lt;p&gt;Click the GitHub button and connect your GitHub account to create a repository.&lt;br&gt;
If it’s your first time, just follow Lovable’s guided steps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97h7xnk7qmo5gtaoptvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97h7xnk7qmo5gtaoptvz.png" alt="link to github" width="800" height="291"&gt;&lt;/a&gt;&lt;br&gt;
Once the repo is generated, Lovable will give you a link, open it on GitHub and download the code as a ZIP file.&lt;/p&gt;

&lt;p&gt;Inside the ZIP, locate your dist folder (you’ll need to build it through your Terminal) - that’s the production-ready build.&lt;/p&gt;

&lt;p&gt;Go back to Skapi → File Hosting and upload all the files from the dist folder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1gw43ri9fgbm57v39qt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff1gw43ri9fgbm57v39qt.png" alt="skapi file hosting" width="800" height="345"&gt;&lt;/a&gt;&lt;br&gt;
And that’s it - your Joy Division pre-sale site is live! You can check our version here: &lt;a href="https://joydivision.skapi.com/" rel="noopener noreferrer"&gt;https://joydivision.skapi.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s honestly incredibly fast and simple. And if you want to go further, you absolutely can. You can ask the AI to build a full user dashboard showing upcoming tour details, their position in the waiting list, login/signup flows, and even an admin panel with restricted access so you can manage users and send notifications. Or you can push the design in a more complex or artistic direction. &lt;/p&gt;

&lt;p&gt;With this setup, pretty much anything is possible.&lt;/p&gt;

&lt;p&gt;You can also skip Lovable entirely and ask ChatGPT to generate the HTML code for you. But keep in mind that if you need visual changes later, raw code isn’t nearly as flexible if you don’t know how to code. Most AI models will regenerate the entire page, which often breaks everything you already built. That’s why a visual AI website builder is usually the more practical choice.&lt;/p&gt;

&lt;p&gt;I hope you enjoyed this walkthrough. If you have any questions or need help while following the guide, let us know in the comments or send us an email at &lt;a href="mailto:support@broadwayinc.com"&gt;support@broadwayinc.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And remember: Skapi gives you a single, unified backend you can reuse across every AI-built project.&lt;/p&gt;

&lt;p&gt;Whether you build in Lovable, Bolt, Claude, Cursor, or raw HTML, your backend remains stable, portable, and easy to understand.&lt;/p&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Skapi Helped Me Finally Get a Raise with Just 15 Minutes of Code</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Fri, 14 Nov 2025 10:10:39 +0000</pubDate>
      <link>https://dev.to/skapi_api/how-skapi-helped-me-finally-get-a-raise-with-just-15-minutes-of-code-58ab</link>
      <guid>https://dev.to/skapi_api/how-skapi-helped-me-finally-get-a-raise-with-just-15-minutes-of-code-58ab</guid>
      <description>&lt;p&gt;A few weeks ago, my boss asked me to go through a huge stack of resumes to help shortlist candidates. It sounded simple until I realized just how many there were. I spent hours reading through pages, trying to pick out key skills and relevant experience. It was exhausting.&lt;/p&gt;

&lt;p&gt;So instead of getting buried in paperwork, I did what developers do best, I automated the process.&lt;/p&gt;

&lt;p&gt;The goal was to build a &lt;strong&gt;resume summarizer app&lt;/strong&gt; that could generate short, clear summaries using AI. Since I'm a &lt;a href="https://www.skapi.com/" rel="noopener noreferrer"&gt;Skapi&lt;/a&gt; user, a serverless backend that makes things ridiculously simple, I figured it was the fastest way to pull this off.&lt;/p&gt;

&lt;p&gt;In less than 15 minutes, I had a fully functional web app running on &lt;strong&gt;plain HTML and JavaScript&lt;/strong&gt;, powered by Skapi for authentication and data handling, no server setup, no database stress.&lt;/p&gt;

&lt;p&gt;The result? A clean AI-powered tool that saved hours of manual work and made me look like a productivity hero. My boss was impressed, and let’s just say it paid off in the best way possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Frontend] ←→ [Skapi] ←→ [Google Auth + Gemini API]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr03g69slsp2bz3apik2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr03g69slsp2bz3apik2m.png" alt="CV resume summarizer app UI" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s what we’ll cover in this guide to achieve the screenshot above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Login&lt;/strong&gt; – implemented with Skapi’s OpenID (no backend code required).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini AI Integration&lt;/strong&gt; – to generate smart, professional resume summaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skapi Records&lt;/strong&gt; – to securely save and fetch summaries for each logged-in user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the best part? It all runs entirely on the frontend &lt;strong&gt;no backend servers required&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you’ll see exactly how our “15-minute experiment” ended up impressing the boss enough to get me a raise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Implementation: The Magic Pieces
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Skapi Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;1.Head to Skapi.com and create an account.&lt;br&gt;
2.Once logged in, create a new service in your Dashboard.&lt;br&gt;
3.You’ll find there your unique Service ID&lt;br&gt;
4.Add it to your frontend script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Skapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVICE_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug8hltzwj0puevtq6akb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug8hltzwj0puevtq6akb.jpg" alt="for frontend developers" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These IDs connect your frontend directly to Skapi’s secure backend with no custom server setup required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Configure Secrets in Skapi&lt;/strong&gt;&lt;br&gt;
In your Skapi Dashboard → Client Secrets, add two secrets:&lt;/p&gt;

&lt;p&gt;1.Google OAuth Client Secret&lt;/p&gt;

&lt;p&gt;Name: &lt;code&gt;ggltoken&lt;/code&gt;&lt;br&gt;
Value: Your Google OAuth Client Secret from Google Cloud Console&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fciomwv8takysceggzpm5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fciomwv8takysceggzpm5.png" alt="google 0auth client secret" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.Gemini API Key&lt;/p&gt;

&lt;p&gt;Name: &lt;code&gt;GEMINI_API_KEY&lt;/code&gt;&lt;br&gt;
Value: Your Gemini API key from &lt;a href="https://aistudio.google.com/app/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpwx59uv6eh84izxzqid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpwx59uv6eh84izxzqid.png" alt="gemini api key" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skapi securely stores these secrets and injects them automatically when using &lt;code&gt;clientSecretRequest()&lt;/code&gt; — meaning your frontend never exposes sensitive data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bdlpxyofyiy0253vnru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bdlpxyofyiy0253vnru.png" alt="client secret key" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Set Up Google OAuth&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to the Google Cloud Console:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to APIs &amp;amp; Services → Credentials&lt;/li&gt;
&lt;li&gt;Create a new OAuth 2.0 Client ID&lt;/li&gt;
&lt;li&gt;Set Application Type to Web Application&lt;/li&gt;
&lt;li&gt;Add your redirect URL (e.g., &lt;code&gt;http://localhost:5500/&lt;/code&gt;) under Authorized redirect URIs&lt;/li&gt;
&lt;li&gt;Copy your Client ID and Client Secret&lt;/li&gt;
&lt;li&gt;Add your &lt;code&gt;GOOGLE_CLIENT_ID&lt;/code&gt; to the script:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_SECRET_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ggltoken&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OPENID_LOGGER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REDIRECT_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:5500/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhikadb4y7v8z971et3o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhikadb4y7v8z971et3o.png" alt="google cloud" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Google Login Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Integrating Google OAuth&lt;/p&gt;

&lt;p&gt;A. Redirect User to Google&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;googleLogin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://accounts.google.com/o/oauth2/v2/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?client_id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;redirect_uri=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;REDIRECT_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;response_type=code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;scope=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openid email profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;prompt=consent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;access_type=offline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;state=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;B. Handle the Redirect and Log In Securely&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleRedirect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Exchange auth code for tokens securely via Skapi&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenResp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientSecretRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;clientSecretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CLIENT_SECRET_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://oauth2.googleapis.com/token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Skapi injects this securely&lt;/span&gt;
      &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;REDIRECT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;grant_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;authorization_code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tokenResp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Use OpenID login with the returned token&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openIdLogin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OPENID_LOGGER_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getProfile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Logged in user:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
  Refer to this for more insights &lt;a href="https://docs.skapi.com/authentication/openid-login.html#google-oauth-example" rel="noopener noreferrer"&gt;Google-OAuth-Example&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skapi’s &lt;code&gt;clientSecretRequest()&lt;/code&gt; handles the token exchange securely — the client secret is never exposed in your code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Summarize Resumes with Gemini&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once the user logs in, they can paste their resume text and summarize it using Gemini:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;summarizeBtn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resumeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Please paste your résumé text first.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clientSecretRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;clientSecretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GEMINI_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$CLIENT_SECRET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Summarize this resume in 5 bullet points:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And retrieving past summaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resume_summaries&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;access_group: "private"&lt;/code&gt; setting ensures that only the logged-in user can access their own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Putting It All Together&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s the simplified flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User clicks Login with Google&lt;/li&gt;
&lt;li&gt;Google redirects back → Skapi securely exchanges tokens&lt;/li&gt;
&lt;li&gt;User pastes resume text&lt;/li&gt;
&lt;li&gt;Gemini API summarizes it&lt;/li&gt;
&lt;li&gt;Summary is saved securely to the user’s private database&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What You’ve Built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Secure Google Authentication using OAuth and Skapi&lt;/li&gt;
&lt;li&gt;AI-Powered Summarization with Gemini API&lt;/li&gt;
&lt;li&gt;Private Data Storage per user in Skapi’s database&lt;/li&gt;
&lt;li&gt;100% serverless and frontend-only implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination lets you build production-ready apps without deploying your own backend.&lt;/p&gt;

&lt;p&gt;You can find the whole project in our &lt;a href="https://github.com/broadwayinc/demo_resume_summary" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>When Telegram Cocoon Goes Live: The Future of the AI Internet</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Fri, 31 Oct 2025 09:39:35 +0000</pubDate>
      <link>https://dev.to/skapi_api/when-telegram-cocoon-goes-live-the-future-of-the-ai-internet-2e67</link>
      <guid>https://dev.to/skapi_api/when-telegram-cocoon-goes-live-the-future-of-the-ai-internet-2e67</guid>
      <description>&lt;p&gt;At &lt;strong&gt;Blockchain Life 2025&lt;/strong&gt; in Dubai, &lt;strong&gt;Telegram CEO Pavel Durov&lt;/strong&gt; unveiled &lt;strong&gt;Cocoon (Confidential Compute Open Network)&lt;/strong&gt; — a decentralized AI compute network built on &lt;strong&gt;The Open Network (TON)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cocoon will allow GPU owners around the world to contribute computing power for AI inference and get rewarded in &lt;strong&gt;Toncoin (TON)&lt;/strong&gt;. Telegram itself will become Cocoon’s first large-scale user, integrating this technology into its &lt;strong&gt;Mini Apps and AI tools&lt;/strong&gt; for its &lt;strong&gt;billion-plus users&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cocoon in a Nutshell
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compute&lt;/strong&gt;: Decentralized AI tasks running securely across a distributed network of GPUs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incentives&lt;/strong&gt;: GPU miners are rewarded in TON for powering these computations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Privacy&lt;/strong&gt;: Data remains encrypted end-to-end — even from the machines processing it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Goal&lt;/strong&gt;: To challenge centralized AI infrastructure monopolies like AWS and Azure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cocoon represents a privacy-first approach to AI infrastructure, aligning with Telegram and TON’s shared vision of &lt;strong&gt;user-owned digital freedom&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem of Cocoon
&lt;/h2&gt;

&lt;p&gt;While Cocoon decentralizes &lt;strong&gt;AI compute&lt;/strong&gt;, building real applications requires much more than inference.&lt;/p&gt;

&lt;p&gt;Developers still need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle user authentication&lt;/li&gt;
&lt;li&gt;Manage data persistence&lt;/li&gt;
&lt;li&gt;Store and serve user-generated content&lt;/li&gt;
&lt;li&gt;Host frontend interfaces&lt;/li&gt;
&lt;li&gt;Securely connect all these components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cocoon offers a distributed engine for AI workloads — but it doesn’t solve the &lt;strong&gt;data and logic layer&lt;/strong&gt; that connects these models to real users and applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Layer of Decentralized Infrastructure
&lt;/h2&gt;

&lt;p&gt;Telegram will be Cocoon’s first client, embedding it into its vast ecosystem of Mini Apps.&lt;/p&gt;

&lt;p&gt;But as the network expands, developers and businesses will be the next wave of adopters.&lt;/p&gt;

&lt;p&gt;To participate in that ecosystem — to build AI-powered apps that leverage Cocoon’s decentralized compute — you’ll need a backend that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manages your users securely&lt;/li&gt;
&lt;li&gt;Stores their data without central control&lt;/li&gt;
&lt;li&gt;Authenticates AI access and app permissions&lt;/li&gt;
&lt;li&gt;Scales automatically as your app grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s exactly what &lt;a href="https://www.skapi.com/" rel="noopener noreferrer"&gt;Skapi&lt;/a&gt; provides. &lt;strong&gt;Skapi&lt;/strong&gt; provides a &lt;strong&gt;serverless backend for web apps&lt;/strong&gt;, purpose-built for the privacy-centric future Cocoon is enabling.&lt;/p&gt;

&lt;p&gt;Developers using Skapi get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication &amp;amp; identity&lt;/strong&gt;: Secure user accounts without traditional servers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Databases&lt;/strong&gt;: Structured data storage with private, encrypted access&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File hosting&lt;/strong&gt;: For AI models, assets, and user uploads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero DevOps&lt;/strong&gt;: No configuration, no server maintenance, no central authority&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skapi already supports a &lt;strong&gt;decentralized approach to data&lt;/strong&gt;: users own their data, and developers never need to expose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A New Web Stack for Decentralized AI Apps
&lt;/h2&gt;

&lt;p&gt;It’s important to note that &lt;strong&gt;Cocoon doesn’t store user data&lt;/strong&gt; — it only processes it securely.&lt;/p&gt;

&lt;p&gt;That means your application still needs a trusted layer to &lt;strong&gt;store, recall, and protect user information&lt;/strong&gt; before and after AI computation happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz5gcht159di1ye3q9k3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyz5gcht159di1ye3q9k3.png" alt="New Web Stack for Decentralized AI Apps" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This architecture keeps every layer decentralized, private, and user-owned.&lt;/p&gt;

&lt;p&gt;This is what we can start calling &lt;strong&gt;Private AI-as-a-Service (PAaaS)&lt;/strong&gt; — a model where both compute and storage are user-owned and secured.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developers Build AI Web3 Applications
&lt;/h2&gt;

&lt;p&gt;With Cocoon and Skapi together, developers can finally build &lt;strong&gt;truly independent applications&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI-powered bots&lt;/strong&gt; that process private data without central servers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mini Apps&lt;/strong&gt; that run entirely on decentralized compute and storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Full-stack businesses&lt;/strong&gt; where both compute and data belong to the users&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of Skapi as your bridge into Cocoon’s ecosystem — the part that allows you to manage your users, data, and authentication &lt;strong&gt;without betraying the principles of decentralization&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because if your AI runs on a decentralized GPU network, your users still log in through a centralized database, you’re &lt;strong&gt;only halfway free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s where Skapi bridges the gap, enabling developers to build full-stack decentralized apps that align perfectly with Cocoon’s vision:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An internet where AI is private, infrastructure is open, and ownership belongs to the user.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

&lt;p&gt;Let us know in the comments what's your vision of future Internet!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>cloud</category>
    </item>
    <item>
      <title>The Meme Game That Changed How We Build Secure APIs — Skapi’s Story</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Fri, 17 Oct 2025 09:54:16 +0000</pubDate>
      <link>https://dev.to/skapi_api/the-cat-that-changed-api-rules-164p</link>
      <guid>https://dev.to/skapi_api/the-cat-that-changed-api-rules-164p</guid>
      <description>&lt;h2&gt;
  
  
  How a meme app improved Skapi’s backend logic (and made our cat very happy)
&lt;/h2&gt;

&lt;p&gt;It started as a joke.&lt;/p&gt;

&lt;p&gt;We wanted to make a &lt;a href="https://shouldifeedthecat.skapi.com/" rel="noopener noreferrer"&gt;tiny web app&lt;/a&gt; called “&lt;strong&gt;Should I Feed the Cat&lt;/strong&gt;?” - a pixel-art voting game where anyone could click &lt;strong&gt;YES&lt;/strong&gt; or &lt;strong&gt;NO&lt;/strong&gt;, watch the scores rise in real time, and decide the fate of a digital cat.&lt;/p&gt;

&lt;p&gt;But that dumb little cat ended up exposing something much bigger: a flaw in how we handled &lt;strong&gt;public data submission&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The meme cat web app in question:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g4bk6biw494cafgv05v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g4bk6biw494cafgv05v.png" alt="meme cat" width="800" height="612"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the process, this little web application changed how &lt;strong&gt;Skapi&lt;/strong&gt;, our secure backend platform, works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Login Walls vs. Public Data
&lt;/h2&gt;

&lt;p&gt;Originally, Skapi was designed with strict security in mind.&lt;/p&gt;

&lt;p&gt;Everything a user sent to the database had to come from a logged-in session. That made sense for apps dealing with sensitive or user-specific data, things like notes, messages, or private uploads.&lt;/p&gt;

&lt;p&gt;But for our cat game, this was absurd.&lt;/p&gt;

&lt;p&gt;Did we really need to force people to &lt;strong&gt;sign up and log in&lt;/strong&gt; just to click “YES, feed the cat”?&lt;/p&gt;

&lt;p&gt;The cat didn’t care who you were.&lt;br&gt;
It just wanted food.&lt;/p&gt;

&lt;p&gt;Yet our system said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“postRecord(): Login is required.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the moment we realized something was off. The world needed a way to collect &lt;strong&gt;anonymous, public data&lt;/strong&gt; safely.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Security is a Problem
&lt;/h2&gt;

&lt;p&gt;“Should I Feed the Cat?” was supposed to be a lighthearted test of Skapi’s frontend integration.&lt;/p&gt;

&lt;p&gt;We added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pixel-style cats (waiting, happy, dead)&lt;/li&gt;
&lt;li&gt;Eye blinks and tear animations&lt;/li&gt;
&lt;li&gt;Confetti explosions for 10,000 “YES” votes&lt;/li&gt;
&lt;li&gt;Thunderstorms if the “NO” side wins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each visitor could spam votes as much as they wanted.&lt;br&gt;
It was chaotic, hilarious, and strangely addictive.&lt;/p&gt;

&lt;p&gt;But when we deployed it, reality hit:&lt;br&gt;
No one could vote unless they logged in.&lt;/p&gt;

&lt;p&gt;The backend was doing its job — protecting data integrity — but in this case, &lt;strong&gt;the protection was the problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We wanted people to interact instantly and just have fun without login.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Breakthrough: Public postRecord()
&lt;/h2&gt;

&lt;p&gt;That’s when we realized something powerful.&lt;/p&gt;

&lt;p&gt;We could safely open part of Skapi’s data system to &lt;strong&gt;public anonymous writes&lt;/strong&gt;, without exposing user data or compromising backend security.&lt;/p&gt;

&lt;p&gt;How?&lt;/p&gt;

&lt;p&gt;By allowing the &lt;code&gt;postRecord()&lt;/code&gt; method to accept submissions from unauthenticated users when writing to public tables.&lt;/p&gt;

&lt;p&gt;Here’s how it looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;choice&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cat_votes_open&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`choice.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s easy to just let anonymous users post data to a backend, but doing it safely was the hard part.&lt;/p&gt;

&lt;p&gt;We needed a way to ensure that anonymous users couldn’t access restricted tables or modify anyone else’s data.&lt;/p&gt;

&lt;p&gt;Normally, Skapi enforces permissions using profile information from a user’s JWT token. But since anonymous users don’t have one, Skapi now generates a randomized, temporary security hash derived from each client’s public address.&lt;/p&gt;

&lt;p&gt;Behind the scenes, Skapi also fully separates &lt;strong&gt;public tables&lt;/strong&gt; from &lt;strong&gt;restricted tables&lt;/strong&gt;, ensuring that anonymous submissions can only write new records but never edits or deletes existing ones. Each anonymous client is tracked as its own isolated device identity, allowing open participation without compromising data integrity or security.&lt;/p&gt;

&lt;p&gt;That’s it. No login. No auth token. The cat finally got fed.&lt;/p&gt;

&lt;p&gt;Under the hood, Skapi still handles all security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Public tables are isolated from private ones.&lt;/li&gt;
&lt;li&gt;No user data is exposed.&lt;/li&gt;
&lt;li&gt;The backend validates every request.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rate limits and data shape protections still apply.&lt;br&gt;
In short — &lt;strong&gt;anyone can submit data, but no one can break the system.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Technical Key of Viral Web Apps
&lt;/h2&gt;

&lt;p&gt;This isn’t just about a cat. It’s about lowering friction in web apps that rely on &lt;em&gt;public engagement&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Think:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polls &amp;amp; surveys&lt;/li&gt;
&lt;li&gt;Comment walls&lt;/li&gt;
&lt;li&gt;Feedback widgets&lt;/li&gt;
&lt;li&gt;Micro games&lt;/li&gt;
&lt;li&gt;Anonymous reactions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all these, forcing logins just kills the vibe.&lt;br&gt;
But letting the world interact freely, while keeping the backend secure, is how you make &lt;strong&gt;lightweight, viral apps&lt;/strong&gt; that spread fast.&lt;/p&gt;

&lt;p&gt;With this Skapi update, developers can now collect &lt;strong&gt;public, anonymous data safely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s something few backends do right.&lt;/p&gt;
&lt;h2&gt;
  
  
  Secure vs. Accessible Web Application
&lt;/h2&gt;

&lt;p&gt;Our little meme experiment revealed a gap between “secure” and “accessible.” And fixing it made Skapi better for everyone.&lt;/p&gt;

&lt;p&gt;Now, anyone can use &lt;code&gt;skapi.postRecord()&lt;/code&gt; to collect open data, whether it’s a public poll, an art project, or a crowdsourced idea board.&lt;/p&gt;

&lt;p&gt;We built a cat game. The cat changed our API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you want to play (or feed the cat), you can check it out here: &lt;a href="https://shouldifeedthecat.skapi.com/" rel="noopener noreferrer"&gt;https://shouldifeedthecat.skapi.com/&lt;/a&gt;, or build your own version in minutes.&lt;/p&gt;

&lt;p&gt;Here’s the minimal setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://cdn.jsdelivr.net/npm/skapi-js@latest/dist/skapi.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Skapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SERVICE_ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;vote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postRecord&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;choice&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public_votes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`choice.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/script&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s how a real backend logic with zero setup works.&lt;/p&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
      <category>security</category>
    </item>
    <item>
      <title>Skapi vs Firebase vs Supabase: Choosing the Best Backend for Performance and Scalability</title>
      <dc:creator>Skapi</dc:creator>
      <pubDate>Thu, 02 Oct 2025 10:33:15 +0000</pubDate>
      <link>https://dev.to/skapi_api/skapi-vs-firebase-vs-supabase-choosing-the-best-backend-for-performance-and-scalability-27e9</link>
      <guid>https://dev.to/skapi_api/skapi-vs-firebase-vs-supabase-choosing-the-best-backend-for-performance-and-scalability-27e9</guid>
      <description>&lt;p&gt;Choosing the right backend service is crucial for the performance, scalability, and user experience of your application. From query response times to concurrent user handling and file upload speeds, your backend directly impacts how smooth, fast, and reliable your app feels.&lt;/p&gt;

&lt;p&gt;Pick the wrong backend, and you risk getting locked into infrastructure that slows down your app, limits scalability, and frustrates users with lag and downtime. Pick the right one, and you’re set up for growth and speed from day one.&lt;/p&gt;

&lt;p&gt;While Firebase and Supabase are popular backend-as-a-service (BaaS) platforms, their added complexity can introduce performance overhead. Skapi takes a different approach: offering speed and scalability out of the box. &lt;/p&gt;

&lt;p&gt;In this article, we’ll compare Firebase, Supabase, and Skapi across three key backend performance metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query Response Times (basic and complex)&lt;/li&gt;
&lt;li&gt;Concurrent User Handling (scalability under load)&lt;/li&gt;
&lt;li&gt;File Upload Speeds (media-heavy app performance)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Query Response Times
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Basic Queries:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Basic query response time is the foundation of backend performance. If it lags, your whole app feels broken, from dashboards to search results to profile loads.&lt;/p&gt;

&lt;p&gt;Here’s the query we will be requesting to the backend:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skapi:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fetch_limit&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Supabase:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch_limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Firebase:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fetch_limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will do simple data fetching query testing with various ranges of fetch limits.&lt;/p&gt;

&lt;p&gt;In benchmark tests, Skapi consistently outperforms Firebase and Supabase in basic query speed, making apps feel instantly responsive without extra optimization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrn8xvkq0cvcyvkkaaed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffrn8xvkq0cvcyvkkaaed.png" alt="Firebase Supabase Skapi benchmark tests" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex Queries:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Complex queries such as joins, sorts, or aggregations, push systems harder. Firebase and Supabase tend to slow down as complexity grows, while Skapi maintains low latency even under demanding query loads.&lt;/p&gt;

&lt;p&gt;Each service uses a different underlying database model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Skapi&lt;/strong&gt;: NoSQL with auto-indexing and client-defined access control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firebase&lt;/strong&gt;: NoSQL document store (Firestore) with structured indexes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt;: SQL/Postgres with schema-driven indexes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To compare, here are equivalent examples of range queries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skapi:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test_index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Supabase:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-&amp;gt;test_index_value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-&amp;gt;test_index_value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Firebase:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test_index_value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test_index_value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Notes on Indexing and Joins&lt;/strong&gt;&lt;br&gt;
Index condition queries rely on conditional lookups using comparison operators &lt;code&gt;(&amp;gt;, &amp;lt;, &amp;gt;=, &amp;lt;=)&lt;/code&gt; on indexed fields.&lt;/p&gt;

&lt;p&gt;For more complex operations such as joins, sorts, or aggregations, the approach differs by platform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skapi&lt;/strong&gt;: Supports indexed conditional lookups directly, but for multi-table relationships or advanced aggregations, data is typically fetched in ranges and then joined or aggregated on the client.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Firebase (Firestore)&lt;/strong&gt;: Works similarly - queries can filter indexed fields efficiently, but there is no native join support. Developers usually fetch multiple datasets and merge them client-side. Composite indexes must be explicitly created for more complex queries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supabase (Postgres)&lt;/strong&gt;: Can handle joins, sorts, and aggregations directly in SQL, but these operations are heavier on the backend. While powerful, they often introduce more latency compared to client-side merges, especially under load.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o84pp7dyz3xn2kp68qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o84pp7dyz3xn2kp68qz.png" alt="Indexing and joins query" width="800" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Skapi ensures your app’s navigation, filtering, and data-heavy features remain fast and smooth, no matter the complexity.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Concurrent User Handling
&lt;/h2&gt;

&lt;p&gt;Scalability isn’t just about spinning up more servers, it’s about maintaining performance under heavy load. Some backends choke as traffic scales.&lt;/p&gt;

&lt;p&gt;In stress tests, Skapi’s backend architecture holds steady, delivering fast, consistent response times even with thousands of concurrent users. This makes it ideal for apps expecting growth, viral traffic spikes, or global audiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples of concurrent test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sends multiple requests simultaneously&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;concurrentCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;skapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRecords&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;performance_test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;access_group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// All execute at once&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F211zv6radv7yp2syjwko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F211zv6radv7yp2syjwko.png" alt="concurrent user handling" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Skapi, you get true horizontal scalability, meaning your app grows seamlessly without compromising speed or user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notes on Concurrency&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skapi&lt;/strong&gt;: Designed with serverless elasticity; concurrency scaling is handled automatically by the platform.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt;: Concurrency depends on underlying Postgres + connection pooling; under extreme load, queries may queue or slow down.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Firebase&lt;/strong&gt;: Firestore handles a high degree of concurrent reads and writes, but performance can degrade if many clients hit the same document paths, since hot-spotting leads to throttling.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. File Upload Speeds
&lt;/h2&gt;

&lt;p&gt;For apps handling images, video, or large files, upload performance is critical. No user wants to stare at a stuck progress bar.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firebase and Supabase can slow down significantly on large file uploads.&lt;/li&gt;
&lt;li&gt;Skapi maintains consistently fast uploads across small and large files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsaci9ofbszu5n2ax0fo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsaci9ofbszu5n2ax0fo.png" alt="file upload speed" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In fact, the performance gap widens as file sizes increase, making Skapi the clear winner for media-heavy apps, SaaS platforms, and social apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Tests Were Conducted
&lt;/h2&gt;

&lt;p&gt;To ensure a fair and transparent comparison, each test followed structured benchmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Query Test&lt;/strong&gt;: Measured how quickly backends respond to simple database queries (profile loads, product lists, or recent posts). Average, min, and max response times were recorded, with success rates checked for 100% delivery.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complex Query Test&lt;/strong&gt;: Ran 16 variations across 8 categories, including index lookups, range queries, tag searches, sorting, aggregations, and multi-filters. Data sets included realistic fields like price, rating, category, and tags.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrent User Test&lt;/strong&gt;: Simulated loads from 5 to 100,000 simultaneous users, measuring how response times and success rates held up under spikes (such as flash sales, viral content, or product launches).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File Upload Test&lt;/strong&gt;: Uploaded files from 1KB to 1GB, tracking average speeds and time-to-completion. These mirrored real-world use cases such as profile images, PDFs, audio, and full HD videos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All tests were repeated multiple times under identical conditions to rule out anomalies. Data sets were auto-cleaned after runs to ensure fair results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Skapi Wins on Performance
&lt;/h2&gt;

&lt;p&gt;Across all three categories  - query response time, concurrent user handling, and file uploads, Skapi outperforms Firebase and Supabase.&lt;/p&gt;

&lt;p&gt;The key technical advantages that set Skapi apart are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fully serverless infrastructure&lt;/strong&gt;: elastic scaling without servers to deploy or manage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NoSQL with unique indexing&lt;/strong&gt;: optimized for high-speed lookups and range queries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Direct client-to-backend access&lt;/strong&gt;: no middle-layer bottlenecks slowing things down&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By choosing Skapi, you’re building on a backend that guarantees speed, scalability, and responsiveness from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts: Skapi vs Firebase vs Supabase
&lt;/h2&gt;

&lt;p&gt;If your app’s success depends on speed, scalability, and user experience, Skapi is the backend built for you. While Firebase and Supabase are solid options, their overhead can slow you down as your project scales.&lt;/p&gt;

&lt;p&gt;Skapi delivers performance superiority across the board, ensuring your app stays fast, reliable, and scalable, whether you’re building a social media platform, SaaS product, or media-heavy mobile app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short&lt;/strong&gt;: With Skapi, there are no delays, no bottlenecks, and no compromises, just the backend performance your project deserves.&lt;/p&gt;

&lt;p&gt;Follow us on socials: &lt;a href="https://x.com/skapi_api" rel="noopener noreferrer"&gt;X&lt;/a&gt;, &lt;a href="https://www.youtube.com/@skapi_official" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, &lt;a href="https://www.instagram.com/skapi_api" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;, &lt;a href="https://www.linkedin.com/company/skapi-backend-api/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. More tutorials, code drops, and dev hacks are coming soon.&lt;/p&gt;

</description>
      <category>database</category>
      <category>testing</category>
      <category>backend</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
