<?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: Hassan takruri</title>
    <description>The latest articles on DEV Community by Hassan takruri (@hassan_takruri_e894957a50).</description>
    <link>https://dev.to/hassan_takruri_e894957a50</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%2F4106719%2Fe7e2be06-fa4a-465b-9abf-9103d186ef98.png</url>
      <title>DEV Community: Hassan takruri</title>
      <link>https://dev.to/hassan_takruri_e894957a50</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hassan_takruri_e894957a50"/>
    <language>en</language>
    <item>
      <title>Baseline – a production FastAPI starter kit</title>
      <dc:creator>Hassan takruri</dc:creator>
      <pubDate>Wed, 02 Sep 2026 18:44:46 +0000</pubDate>
      <link>https://dev.to/hassan_takruri_e894957a50/baseline-a-production-fastapi-starter-kit-1jp5</link>
      <guid>https://dev.to/hassan_takruri_e894957a50/baseline-a-production-fastapi-starter-kit-1jp5</guid>
      <description>&lt;h1&gt;
  
  
  What a "production-ready" FastAPI starter actually needs
&lt;/h1&gt;

&lt;p&gt;Every FastAPI project I've started begins the same way: an hour of&lt;br&gt;
boilerplate before I write a single line of actual logic. Auth. A&lt;br&gt;
database session dependency. A folder structure that won't fall apart&lt;br&gt;
once there's more than one resource. A test setup that doesn't take&lt;br&gt;
longer to configure than the tests themselves.&lt;/p&gt;

&lt;p&gt;I got tired of rebuilding it, so I built it once, properly, and wrote&lt;br&gt;
down why each piece is shaped the way it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The structure
&lt;/h2&gt;

&lt;p&gt;Every resource in the project follows the same four layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Router&lt;/strong&gt; — HTTP in/out only. Parses the request, calls a service,
serializes the response. No business logic lives here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service&lt;/strong&gt; — business rules. Ownership checks, "does this already
exist" decisions, orchestration. No FastAPI imports — this layer
doesn't know it's running inside a web framework.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt; — persistence only. SELECT/INSERT/UPDATE/DELETE via
SQLAlchemy. No business rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt; — Pydantic models for request/response shapes, kept
separate from the ORM models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This feels like overkill for a single resource. It stops feeling that&lt;br&gt;
way the first time you need the same ownership check enforced in two&lt;br&gt;
different routes, or the first time you want to unit-test a business&lt;br&gt;
rule without spinning up the whole ASGI app to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decisions that actually mattered
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Testing against real Postgres, not SQLite.&lt;/strong&gt; A SQLite-backed test&lt;br&gt;
suite gives you false confidence — native UUID types, enum handling,&lt;br&gt;
and constraint behavior all differ enough that "tests pass" stops&lt;br&gt;
meaning "the Postgres-specific code works." Each test runs inside a&lt;br&gt;
SAVEPOINT that gets rolled back afterward, so isolation doesn't cost&lt;br&gt;
a schema rebuild per test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two token types, not one.&lt;/strong&gt; Short-lived access tokens (15 min) plus&lt;br&gt;
longer-lived refresh tokens (30 days), with the token's &lt;code&gt;type&lt;/code&gt; claim&lt;br&gt;
checked on every decode — a refresh token presented where an access&lt;br&gt;
token is expected gets rejected on that alone, not just on signature&lt;br&gt;
validity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One error shape, always.&lt;/strong&gt; A raised domain exception, a FastAPI&lt;br&gt;
validation error, and an unhandled 500 all come back as&lt;br&gt;
&lt;code&gt;{"error": {"code": ..., "message": ..., "details": ...}}&lt;/code&gt;. A frontend&lt;br&gt;
or API client shouldn't need three different error-handling code&lt;br&gt;
paths depending on which layer failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Argon2, not bcrypt.&lt;/strong&gt; Not because bcrypt is broken, but because&lt;br&gt;
&lt;code&gt;passlib&lt;/code&gt; — the usual FastAPI-tutorial wrapper around it — has been&lt;br&gt;
effectively unmaintained and throws deprecation warnings on current&lt;br&gt;
Python. &lt;code&gt;argon2-cffi&lt;/code&gt; directly, no wrapper.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't build
&lt;/h2&gt;

&lt;p&gt;No Stripe integration, no multi-tenancy, no admin panel, no frontend.&lt;br&gt;
Not because they're hard — because bundling them in means paying for&lt;br&gt;
scope you don't need yet, and a starter kit that tries to be&lt;br&gt;
everything ends up being a worse version of each thing. This is&lt;br&gt;
meant to be the backend those get built on top of, not a replacement&lt;br&gt;
for building them properly when you actually need them.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want the whole thing
&lt;/h2&gt;

&lt;p&gt;I packaged this into &lt;strong&gt;Baseline&lt;/strong&gt; — JWT auth, async SQLAlchemy 2.0 +&lt;br&gt;
PostgreSQL, the layered structure above with a fully-wired example&lt;br&gt;
resource, a 20-test suite, Docker Compose, and GitHub Actions CI,&lt;br&gt;
documented rather than just dumped. $12, unlimited personal and&lt;br&gt;
commercial projects: &lt;strong&gt;[&lt;a href="https://hassantak.gumroad.com/l/baseline" rel="noopener noreferrer"&gt;https://hassantak.gumroad.com/l/baseline&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the decisions above in the&lt;br&gt;
comments — including the ones I'd probably do differently next time.&lt;br&gt;
I packaged this into &lt;strong&gt;Baseline&lt;/strong&gt; — JWT auth, async SQLAlchemy 2.0 +&lt;br&gt;
PostgreSQL, the layered structure above with a fully-wired example&lt;br&gt;
resource, a 20-test suite, Docker Compose, and GitHub Actions CI,&lt;br&gt;
documented rather than just dumped. $12, unlimited personal and&lt;br&gt;
commercial projects: &lt;strong&gt;[link to your Gumroad listing]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about any of the decisions above in the&lt;br&gt;
comments — including the ones I'd probably do differently next time.``&lt;/p&gt;

</description>
      <category>developer</category>
      <category>fastapi</category>
      <category>backend</category>
      <category>fullstack</category>
    </item>
  </channel>
</rss>
