<?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: moogo.dev</title>
    <description>The latest articles on DEV Community by moogo.dev (@moogodev).</description>
    <link>https://dev.to/moogodev</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%2F4172377%2F4e6501d6-fd7d-489e-82f2-c38854ac10c3.png</url>
      <title>DEV Community: moogo.dev</title>
      <link>https://dev.to/moogodev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moogodev"/>
    <language>en</language>
    <item>
      <title>Why Moogo?</title>
      <dc:creator>moogo.dev</dc:creator>
      <pubDate>Sat, 10 Oct 2026 03:03:51 +0000</pubDate>
      <link>https://dev.to/moogodev/why-moogo-32jj</link>
      <guid>https://dev.to/moogodev/why-moogo-32jj</guid>
      <description>&lt;p&gt;Most hosted databases are Postgres with a nice wrapper. Moogo is not. It is built&lt;br&gt;
around a different bet: &lt;strong&gt;for a large class of applications, one SQLite file per&lt;br&gt;
project is a better fit than a shared Postgres cluster&lt;/strong&gt;, and the ergonomics of&lt;br&gt;
the API matter more than the feature list of the engine.&lt;/p&gt;

&lt;p&gt;This page explains the reasoning, including the trade-offs that come with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem Moogo is solving
&lt;/h2&gt;

&lt;p&gt;If you are building a serverless function, a small SaaS, an internal tool, or an&lt;br&gt;
AI agent that needs to remember things, you run into the same three problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Connection management is your problem.&lt;/strong&gt; A serverless function has no&lt;br&gt;
long-lived process. Every cold start means a new connection. You end up writing a&lt;br&gt;
connection pool, or putting PgBouncer in front, or paying for a driver that&lt;br&gt;
manages sockets for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A shared database means shared risk.&lt;/strong&gt; In a multi-tenant Postgres, isolation&lt;br&gt;
is a set of row-level security policies. They are well understood and they work —&lt;br&gt;
until a policy is written with one too few &lt;code&gt;WHERE&lt;/code&gt; clauses, and one tenant reads&lt;br&gt;
another tenant's rows. This is not a hypothetical; it is the most common&lt;br&gt;
multi-tenant data breach in the ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. You do not need Postgres.&lt;/strong&gt; A huge fraction of applications are a few&lt;br&gt;
tables, a handful of queries, and a few megabytes of data. What they need is&lt;br&gt;
reliability, speed, and not having to think about the database at all. SQLite has&lt;br&gt;
been the most-used embedded database in the world for over a decade, and it is&lt;br&gt;
fast enough for essentially all of this.&lt;/p&gt;

&lt;p&gt;Moogo takes that bet: &lt;strong&gt;give each project a whole SQLite file, and expose it over&lt;br&gt;
HTTP, and most of the operational work disappears.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The decisions that follow from that
&lt;/h2&gt;
&lt;h3&gt;
  
  
  One file per project, not a shared cluster
&lt;/h3&gt;

&lt;p&gt;This is the decision everything else follows from.&lt;/p&gt;

&lt;p&gt;Isolation becomes structural instead of policy-based. There is no &lt;code&gt;tenant_id&lt;/code&gt; to&lt;br&gt;
filter on because there is no shared table. A query physically cannot reach&lt;br&gt;
another project's data, because it is running against a different file opened by&lt;br&gt;
a different file handle.&lt;/p&gt;

&lt;p&gt;It is also why Moogo is fast. A write goes straight into the project's own file&lt;br&gt;
with no row-level security evaluation, no cross-tenant index contention, and no&lt;br&gt;
neighbour making your &lt;code&gt;INSERT&lt;/code&gt; slow.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prepared statements only, enforced
&lt;/h3&gt;

&lt;p&gt;Values never travel inside your SQL text. They are bound parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SELECT id FROM users WHERE email = ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ketut@example.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine only ever sees a parameterised statement. This means a value can never&lt;br&gt;
become syntax, which is the whole class of SQL injection bugs — not "mostly&lt;br&gt;
prevented", but structurally impossible on this path.&lt;/p&gt;

&lt;p&gt;Every statement is also &lt;strong&gt;tokenized and inspected before it runs&lt;/strong&gt;. &lt;code&gt;ATTACH&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;readfile&lt;/code&gt;, &lt;code&gt;writefile&lt;/code&gt;, &lt;code&gt;load_extension&lt;/code&gt;, stacked statements, and triggers are&lt;br&gt;
rejected outright. See &lt;a href="https://dev.to/docs/security"&gt;Security&lt;/a&gt; for the full list and why each&lt;br&gt;
one is refused.&lt;/p&gt;

&lt;h3&gt;
  
  
  No drivers, no cold starts
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;moogo_...&lt;/code&gt; key and a URL are the entire integration. There is no driver to&lt;br&gt;
install, no version to keep in sync with your runtime, and no connection to leak&lt;br&gt;
when your function is frozen and thawed.&lt;/p&gt;

&lt;p&gt;The project never pauses either. There is no idle timeout, so there is no cold&lt;br&gt;
start to design around and no "first request is slow" to apologise for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keys you can throw away
&lt;/h3&gt;

&lt;p&gt;The project key is shown &lt;strong&gt;once&lt;/strong&gt;, when the project is created and when it is&lt;br&gt;
rotated. What is stored is a SHA-256 hash plus an eight-character prefix.&lt;/p&gt;

&lt;p&gt;The consequence is worth stating plainly: a leaked database dump does not hand an&lt;br&gt;
attacker a list of working keys. A key that appears in a log, a screenshot, or a&lt;br&gt;
public repository is a key you need to rotate, but it is not a key that reveals&lt;br&gt;
the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits you can see
&lt;/h3&gt;

&lt;p&gt;Every request has a time budget and every project has a size ceiling. Both come&lt;br&gt;
back in the response, and both are enforced &lt;em&gt;before&lt;/em&gt; the work is committed rather&lt;br&gt;
than discovered afterwards. A write that would cross the size limit is refused; it&lt;br&gt;
is not silently truncated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs, stated honestly
&lt;/h2&gt;

&lt;p&gt;Moogo is not the right tool for everything, and pretending otherwise would waste&lt;br&gt;
your time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You probably want something else if&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You need &lt;code&gt;PostGIS&lt;/code&gt; or spatial queries&lt;/td&gt;
&lt;td&gt;SQLite has &lt;code&gt;R-Tree&lt;/code&gt;, but it is not PostGIS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need cross-region replicas&lt;/td&gt;
&lt;td&gt;One project is one file on one host&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need concurrent analytical scans&lt;/td&gt;
&lt;td&gt;SQLite is a single-writer engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need a database larger than 100 MB&lt;/td&gt;
&lt;td&gt;That is the ceiling, and it is enforced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need row-level security across tenants&lt;/td&gt;
&lt;td&gt;You do not need it; you get file isolation instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You need &lt;code&gt;GRANT&lt;/code&gt; / &lt;code&gt;REVOKE&lt;/code&gt; and multiple roles&lt;/td&gt;
&lt;td&gt;Moogo has one role per project key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the free tier you also get &lt;strong&gt;2 projects per account&lt;/strong&gt;, 100 MB per database,&lt;br&gt;
and 256 MB of storage per project. There is no billing in the current version, so&lt;br&gt;
every account is on the same plan. If you need more than that, the constraint is&lt;br&gt;
a real one and you should know about it before you build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is deliberately missing
&lt;/h2&gt;

&lt;p&gt;Some things are absent on purpose, not by accident:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No triggers.&lt;/strong&gt; A trigger body contains semicolons, so detecting stacked
statements correctly needs a real parser. Rather than ship a check that fails
open on input SQLite accepts, triggers are declined. Write your invariants in
your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;ATTACH&lt;/code&gt;.&lt;/strong&gt; It would turn the write endpoint into a file reader for the
whole host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No idle pausing.&lt;/strong&gt; It is a feature you would have to design around, and
Moogo is always on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No self-hosting yet.&lt;/strong&gt; It is planned, and it is not in this version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try moogo at &lt;a href="https://moogo.dev" rel="noopener noreferrer"&gt;https://moogo.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>serverless</category>
    </item>
    <item>
      <title>What is Moogo?</title>
      <dc:creator>moogo.dev</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:26:02 +0000</pubDate>
      <link>https://dev.to/moogodev/what-is-moogo-594h</link>
      <guid>https://dev.to/moogodev/what-is-moogo-594h</guid>
      <description>&lt;p&gt;Moogo is a hosted backend that gives every project its own &lt;strong&gt;SQLite database&lt;/strong&gt; and&lt;br&gt;
its own &lt;strong&gt;object storage&lt;/strong&gt;, reachable over plain HTTP. There is no connection&lt;br&gt;
string, no driver, and no database client to install.&lt;/p&gt;

&lt;p&gt;You create a project, and Moogo hands you three values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MOOGO_PROJECT_URL=https://api.moogo.dev/p/8f3c1a20-5b7e-4a91-9d3c-2f6b81e4a7d0
MOOGO_PROJECT_ID=8f3c1a20-5b7e-4a91-9d3c-2f6b81e4a7d0
MOOGO_SECRET_KEY=moogo_...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put those in your application's environment and it can read and write data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nv"&gt;$MOOGO_PROJECT_URL&lt;/span&gt;/query &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$MOOGO_SECRET_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"query":"SELECT id, email FROM users WHERE plan = ?","args":["pro"]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole integration. No SDK, no ORM, no connection pool to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two planes
&lt;/h2&gt;

&lt;p&gt;Everything about Moogo follows from one architectural decision: the service runs&lt;br&gt;
in &lt;strong&gt;two tiers&lt;/strong&gt;, and they never mix.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plane&lt;/th&gt;
&lt;th&gt;Runs on&lt;/th&gt;
&lt;th&gt;Holds&lt;/th&gt;
&lt;th&gt;Touches your data?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control plane&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Accounts, projects, key hashes, quotas, the bucket catalog, activity logs&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data plane&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SQLite, one &lt;code&gt;.db&lt;/code&gt; file per project&lt;/td&gt;
&lt;td&gt;Your tables and your rows&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The control plane knows that a project exists. The data plane holds what is in it.&lt;br&gt;
A query never crosses between them, and a bug in the control plane cannot reach&lt;br&gt;
your rows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters to you
&lt;/h3&gt;

&lt;p&gt;Because the data plane is &lt;strong&gt;one physical file per project&lt;/strong&gt;, isolation is not a&lt;br&gt;
policy that can be misconfigured. Project A and project B are two different files&lt;br&gt;
on disk. There is no shared connection, no shared buffer pool, and no query that&lt;br&gt;
can be written to cross from one to the other. A project belonging to another&lt;br&gt;
account answers &lt;code&gt;404&lt;/code&gt;, not &lt;code&gt;403&lt;/code&gt;, so an id cannot even be probed for existence.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real SQLite database.&lt;/strong&gt; Full SQLite, the engine you already know, with
&lt;code&gt;WAL&lt;/code&gt; journaling and foreign keys enabled on every connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object storage per project.&lt;/strong&gt; Up to 256 MB of files, organized into buckets,
with public URLs you can put in an &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dashboard.&lt;/strong&gt; Browse your tables as a spreadsheet, run SQL in a console, and
watch the activity log. It needs no secret key from you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest limits.&lt;/strong&gt; 100 MB per database, 15 seconds per statement, 1 MB per
request body. Every one of them is reported in the response, so you find out
from the API rather than from a hung tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Moogo is not
&lt;/h2&gt;

&lt;p&gt;It is worth being direct about the edges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not Postgres.&lt;/strong&gt; If you need &lt;code&gt;PostGIS&lt;/code&gt;, logical replication, or
concurrent analytical workloads, Moogo is the wrong tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not a global replica network.&lt;/strong&gt; One project is one file on one host.
There is no read replica in another region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not pause.&lt;/strong&gt; A project does not go to sleep after inactivity, so
there is no cold start and no "first request after idle" latency. It also never
pauses on its own — if you want to stop paying resources for an unused project,
you pause it deliberately.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visit &lt;a href="https://moogo.dev" rel="noopener noreferrer"&gt;https://moogo.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>backend</category>
      <category>cloud</category>
      <category>database</category>
    </item>
  </channel>
</rss>
