<?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: Alexander Valenchits</title>
    <description>The latest articles on DEV Community by Alexander Valenchits (@valenchits).</description>
    <link>https://dev.to/valenchits</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3890484%2F9d34751a-4a02-4020-a950-8ffe4891f38c.jpg</url>
      <title>DEV Community: Alexander Valenchits</title>
      <link>https://dev.to/valenchits</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/valenchits"/>
    <language>en</language>
    <item>
      <title>Why I Use Two AI Assistants at Once: My Experience with Perplexity Pro and Kimi Allegretto</title>
      <dc:creator>Alexander Valenchits</dc:creator>
      <pubDate>Sat, 02 May 2026 10:51:05 +0000</pubDate>
      <link>https://dev.to/valenchits/why-i-use-two-ai-assistants-at-once-my-experience-with-perplexity-pro-and-kimi-allegretto-23oo</link>
      <guid>https://dev.to/valenchits/why-i-use-two-ai-assistants-at-once-my-experience-with-perplexity-pro-and-kimi-allegretto-23oo</guid>
      <description>&lt;p&gt;Over the past few months, I’ve been deliberately paying for two AI services: Perplexity Pro and Kimi Allegretto. On the surface, that probably looks strange—even to me—but in practice they fill different roles. In this article, I want to share my personal experience: where Perplexity is genuinely stronger than a classic “chat with a model,” where Kimi turns out to be more useful, and what I end up criticizing in each of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Pay for AI Assistants at All
&lt;/h2&gt;

&lt;p&gt;I regularly do more than just coding—I also spend a lot of time on research and self-education: tracking who is launching what, which pricing plans are appearing, and how AI products and architectures are evolving. On top of that, I’m constantly reading articles, documentation, blogs, and technical deep dives. Standard assistants at the level of an “IDE helper” like Cursor and similar tools are not enough for this: they help write code, but they do not really handle deep research or help build a broad picture of the market.&lt;/p&gt;

&lt;p&gt;At some point it became clear that I needed a separate layer in my stack for research and for working with large volumes of text and code. That’s how Perplexity Pro and Kimi Allegretto entered my workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Perplexity Pro: an answer engine, research tool, and “browser with a brain”
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Deep Research and skills
&lt;/h3&gt;

&lt;p&gt;After a month of heavy use, Perplexity stopped feeling like “just another chatbot” and became a dedicated research tool for me. What I liked most was Deep Research: it can pull together insights from dozens of articles, attach source links, and re-check its own conclusions, turning large amounts of information into a coherent overview of a topic. If you give it meaningful context—not “find something somewhere on the internet,” but a clearly defined task with boundaries and nuances—it can produce a detailed and careful analysis.&lt;/p&gt;

&lt;p&gt;A separate story is the skills system. Perplexity lets you connect custom skills, including ones found on GitHub, literally as a single file, and then simply tell the model to “use this skill.” I tested this for SEO optimization and various research workflows, and the combination of Deep Research plus skills gives a very noticeable boost in depth and analysis quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Computer and Personal Computer: an AI layer over the system
&lt;/h3&gt;

&lt;p&gt;From a developer’s perspective, I was also impressed by Perplexity Computer with Anthropic’s latest model. It is a separate paid mode available on higher-tier plans, and it launches a full agent with access to the browser, files, and codebase. When I pointed it at a real Git repository and gave it the necessary skills, I got a refactoring result that genuinely felt like it had been done by a human developer: it reviewed the code, organized everything clearly, and ultimately proposed a pull request with understandable changes.&lt;/p&gt;

&lt;p&gt;There is also the separate Personal Computer initiative. Perplexity recently announced the Personal Computer program and opened a waitlist: the idea is to build an “AI operating system” that lives on your Mac, continuously works with local files and apps, and orchestrates tasks in the background. Naturally, I joined the waitlist, but for now it is still a closed product, and the timeline and chances of getting access look rather vague.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comet: a browser with an assistant built in
&lt;/h3&gt;

&lt;p&gt;I also want to highlight Comet, Perplexity’s own browser. It is not just a Chromium fork, but a browser with a built-in assistant that understands page context and can work across multiple tabs like an agent. In practice, it feels like something between Yandex Browser with AI features and a standalone agentic environment.&lt;/p&gt;

&lt;p&gt;As a translator, Comet behaves confidently: it understands context well and preserves both structure and meaning during translation, so reading articles and documentation in other languages feels comfortable. You can also use it as an agent—give it a task like “go through several pages, collect the facts, make a summary, and draft an article or a table,” and Comet will actually move across tabs, extract data, and return something already structured and meaningful. That saves time when you need to turn a set of links into text or organized data quickly.&lt;/p&gt;

&lt;p&gt;That said, Comet has one important limitation: it cannot translate videos such as YouTube clips, while Yandex Browser can at least generate a rough Russian translation after a pause—often enough to understand the gist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multimodality as a nice bonus
&lt;/h3&gt;

&lt;p&gt;In terms of multimodality, Perplexity looks quite decent: thanks to built-in Gemini and GPT models, it can produce reasonable images and basic videos, especially when you need complex prompts or text rendered inside images. But there are still some unclear generation limits tied to plan level and credits, so it does not really work as a primary “visual content factory.” My practical conclusion is that this is a useful layer on top of research, not a replacement for dedicated graphics or video tools.&lt;/p&gt;




&lt;h2&gt;
  
  
  The downsides of Perplexity: limits, plans, and no IDE integration
&lt;/h2&gt;

&lt;p&gt;Despite all the strengths, Perplexity has several drawbacks that are critical for me.&lt;/p&gt;

&lt;p&gt;First, there is the &lt;strong&gt;complete lack of transparency around limits&lt;/strong&gt;. As a user, I do not fully understand how many Deep Research runs, Computer sessions, or tokens I still have left: the dashboard does not provide a clear enough usage overview. Sometimes the service suddenly refuses access to certain features in the middle of the week, even though the billing month is not over yet, and from the outside it looks like random disabling without a clear counter. If you care about planning workload and budget, this is much more irritating than it should be.&lt;/p&gt;

&lt;p&gt;Second, there is the &lt;strong&gt;pricing structure&lt;/strong&gt;. Right now, there is basically a reasonably priced Pro plan and then a jump straight to Max/Computer-level plans at around $200+ per month, where Computer access and much higher limits are included.&lt;/p&gt;

&lt;p&gt;Third, there is the &lt;strong&gt;lack of native VS Code integration&lt;/strong&gt;. Compared with Kimi, which has Kimi Code and an official IDE extension, Perplexity feels slightly disconnected from my day-to-day development environment. If they introduced a middle-tier plan with basic Computer access plus an official VS Code plugin, Perplexity could become not just an answer engine for me, but a real infrastructure layer around development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kimi Allegretto: agents, IDE integration, and long-running workflows
&lt;/h2&gt;

&lt;h3&gt;
  
  
  An ecosystem built around the developer: Kimi Code and Claw
&lt;/h3&gt;

&lt;p&gt;I started using Kimi Allegretto only recently, but even after a couple of days it became clear that this is not just a chat app—it is more of an ecosystem built around the K2.6 model.&lt;/p&gt;

&lt;p&gt;For a developer, the first thing that stands out is &lt;strong&gt;Kimi Code&lt;/strong&gt; with its native VS Code extension: you can discuss code directly inside the editor, inspect diffs, and apply changes without switching back to the browser. That feels natural because the assistant lives in the same place as the main workflow.&lt;/p&gt;

&lt;p&gt;Another important part of the ecosystem is &lt;strong&gt;Kimi Claw&lt;/strong&gt;. In essence, it is a persistent agent that lives in its own workflow space, has its own folder, stores files, and accumulates experience and skills as work progresses. Unlike disposable chat sessions, Claw builds a knowledge base around your tasks, which makes it easier to return later and continue the flow instead of starting from scratch.&lt;/p&gt;

&lt;p&gt;At the platform level, Kimi feels cohesive: there is a desktop client, mobile apps, and a web interface, so you can access the same agent from any device. The ecosystem also includes web tools for internet access and several agent modes—from document and slide processing to Sheets integrations and Agent Swarm scenarios. I have not had time to test everything yet, but the platform clearly aims to be broader than a simple chat app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Website agent: UI generation out of the box
&lt;/h3&gt;

&lt;p&gt;One specific feature that pleasantly surprised me was the &lt;strong&gt;agent mode for websites&lt;/strong&gt;. In a simple “describe what you want” format, Kimi can produce a pretty decent UI: it can think through page structure, navigation, section copy, and generate interface code. This is not design-award material, of course, but for prototypes and first product versions the output is already usable and feels roughly on the level of modern AI tools in the Vercel/design-to-code ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing and transparency of limits
&lt;/h3&gt;

&lt;p&gt;Compared with Perplexity, Kimi’s pricing feels especially pleasant. There are several tiers, and you can choose a comfortable “middle” package with higher limits instead of jumping straight into something that feels enterprise-grade.&lt;/p&gt;

&lt;p&gt;That is the plan I chose, and so far the limits seem sufficient. The dashboard is also more transparent: it shows how many requests and tokens have already been used, so you can actually see where your quota is going and plan when to run heavy agent tasks or long sessions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The downsides of Kimi: languages, speed, IDE constraints, and expensive multimodality
&lt;/h2&gt;

&lt;p&gt;Kimi also has some serious limitations.&lt;/p&gt;

&lt;p&gt;First, there is the &lt;strong&gt;language issue&lt;/strong&gt;. The ecosystem is clearly optimized for English and Chinese first, and that is noticeable in practice. In my case, Kimi did not handle Russian dictation at all in the way I expected, so I either have to switch to English or type manually. If a large part of your workflow depends on Russian or other less-supported languages, Kimi currently feels much less convenient.&lt;/p&gt;

&lt;p&gt;Second, there are the &lt;strong&gt;limitations of Kimi Code in VS Code&lt;/strong&gt;. In the current implementation, you can only keep one active agent per window: when you launch a new task, the previous one effectively stops. Compared with tools like Codex or Cursor, where parallel development contexts are normal, this is a real step backward in convenience when juggling multiple tasks and repositories at once.&lt;/p&gt;

&lt;p&gt;Third, there is &lt;strong&gt;speed&lt;/strong&gt;. Compared with Codex and Cursor on some tasks, Kimi feels noticeably slower, sometimes by a large margin. The main reason seems to be the thinking mode: when it is enabled, the model produces long reasoning chains. If you turn thinking off, Kimi does become faster, but at the cost of quality: the answers become more superficial, so you constantly have to choose between depth and speed.&lt;/p&gt;

&lt;p&gt;Finally, there is one very practical point: &lt;strong&gt;visual tasks burn through limits quickly&lt;/strong&gt;. In my case, a test website design with 3–4 pages in agent mode consumed around 4–5% of the monthly Allegretto limit, which feels expensive for that scope. Video was even worse: two test runs produced very weak results but consumed roughly a third of the monthly limit. By contrast, normal code-related work seems much more economical. So my practical conclusion is simple: in Kimi, multimodality—especially video—does not currently justify the limit cost, and the tool makes the most sense primarily for text and code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Perplexity Pro&lt;/th&gt;
&lt;th&gt;Kimi Allegretto&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Main focus&lt;/td&gt;
&lt;td&gt;Answer engine and research: Deep Research, citations, verifiable answers&lt;/td&gt;
&lt;td&gt;Long context, agents, and a working environment around code and documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web and sources&lt;/td&gt;
&lt;td&gt;Strong live web search, neat citations, structured summaries&lt;/td&gt;
&lt;td&gt;Web tools exist, but less emphasis on transparent source citation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Comet as a browser with an assistant and translator, but no video translation&lt;/td&gt;
&lt;td&gt;More classic browser-style workflows through web tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code and refactoring&lt;/td&gt;
&lt;td&gt;Skills plus higher-end agent workflows for repository analysis and PR-style refactoring&lt;/td&gt;
&lt;td&gt;Kimi Code with VS Code integration, plus persistent agents such as Claw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multimodality&lt;/td&gt;
&lt;td&gt;Good images and basic video, but with plan and credit limits&lt;/td&gt;
&lt;td&gt;Available, but in my experience expensive in terms of limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plans and limits&lt;/td&gt;
&lt;td&gt;Pro around $20/month, Max around $200/month, and the jump between them is steep&lt;/td&gt;
&lt;td&gt;Multiple tiers, with Allegretto as a workable middle option&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IDE integration&lt;/td&gt;
&lt;td&gt;No native VS Code plugin publicly highlighted in current plan materials&lt;/td&gt;
&lt;td&gt;Kimi Code includes VS Code-oriented workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Languages and dictation&lt;/td&gt;
&lt;td&gt;Better suited to multilingual reading and research in my workflow&lt;/td&gt;
&lt;td&gt;Stronger for English/Chinese, weaker for Russian dictation in my use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speed&lt;/td&gt;
&lt;td&gt;Generally fast, with tolerable delay even in heavier modes&lt;/td&gt;
&lt;td&gt;Slower in thinking mode, with more trade-off between speed and depth&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  My short conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity Pro&lt;/strong&gt; is excellent for research, gives detailed source-backed summaries, can support serious code analysis through its more advanced agent features, and Comet adds a useful browser layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kimi Allegretto&lt;/strong&gt; is more comfortable as an environment for working with code and documents, offers longer-lived agents, has a more explicit mid-tier pricing option, and feels closer to the developer workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What will ultimately stay in my stack, I still have not fully decided. For now, I’m going to keep using both in parallel, compare how they handle the same tasks, and if anything particularly interesting comes out of that, I’ll return with another article.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>We Shipped an MVP With Vibe-Coding. Here's What Nobody Tells You About the Aftermath</title>
      <dc:creator>Alexander Valenchits</dc:creator>
      <pubDate>Tue, 21 Apr 2026 14:25:30 +0000</pubDate>
      <link>https://dev.to/valenchits/we-shipped-an-mvp-with-vibe-coding-heres-what-nobody-tells-you-about-the-aftermath-3lml</link>
      <guid>https://dev.to/valenchits/we-shipped-an-mvp-with-vibe-coding-heres-what-nobody-tells-you-about-the-aftermath-3lml</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This one's for mid-to-senior developers wondering where they stand in the age of AI.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There's an endless debate in the IT world: will AI replace developers? I'm a tech lead at AluSoftBel, and I want to share my perspective — both as a developer and as someone who works directly with business stakeholders. Spoiler: the answer might surprise you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Uncomfortable Truth About AI-Generated Code
&lt;/h2&gt;

&lt;p&gt;Let me be straight: the quality of code AI models produce is poor. But here's the other side of the coin — since AI arrived, businesses have felt the speed of development increase, and now they want more features, faster.&lt;/p&gt;

&lt;p&gt;This creates a classic conflict of interests. Business wants to earn more money. Developers want maintainable, quality code — so we don't shoot ourselves in the foot later. Both sides are right. The question is how to balance them.&lt;/p&gt;




&lt;h2&gt;
  
  
  How We Actually Use AI: A Real-World Workflow
&lt;/h2&gt;

&lt;p&gt;Let me walk you through the approach we landed on at our company.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: MVP First — Quality Later
&lt;/h3&gt;

&lt;p&gt;Step one is always an MVP. Fast, hardcore, zero regard for quality. The only goal: ship a working product that can be sold.&lt;/p&gt;

&lt;p&gt;Yes, you end up with technical debt. Yes, it's messy. But the product hits production quickly, it works — somehow — and that's enough to build on. You can show it at a presale, close a deal, get a budget approved.&lt;/p&gt;

&lt;p&gt;The key insight: an MVP doesn't just give you a demo. It gives you a &lt;em&gt;real working product&lt;/em&gt; where you can see what's strong, what's weak, and course-correct before serious money is invested.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Sell It, Then Plan For Real
&lt;/h3&gt;

&lt;p&gt;Once the product is sold, the tech lead, business analyst, and PM sit down and build a real development plan. I look at technical implementation, the analyst looks at business requirements. We're not guessing anymore — we're working with concrete, real-world cases and filing down what already exists.&lt;/p&gt;

&lt;p&gt;Simultaneously, the business side collects real user feedback: what works, what doesn't, what's frustrating. Not assumptions — live cases. From these, the PM and analyst build a proper spec with clear requirements and a real product vision. Only &lt;em&gt;then&lt;/em&gt; does full-scale development begin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Dealing With the Aftermath
&lt;/h3&gt;

&lt;p&gt;Now comes the technical debt cleanup. In my case, the MVP was pure &lt;strong&gt;vibe-coding&lt;/strong&gt; — a term for AI-assisted, low-oversight rapid prototyping. The code was rewritten 3–4 times, leaving behind dead models, orphaned database tables, unused libraries, and broken legacy code.&lt;/p&gt;

&lt;p&gt;After the cleanup comes decomposition into a proper tech stack — moving everything into a structured architecture based on actual design patterns. From chaos to order. &lt;strong&gt;This is the work AI simply cannot do.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Costs: Pros and Cons
&lt;/h2&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h3&gt;
  
  
  ❌ Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overhead on one or two people.&lt;/strong&gt; Someone has to carry a massive workload alone, fall out of their normal rhythm, work overtime. In my case — that was me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology chosen on the fly.&lt;/strong&gt; Libraries with a handful of GitHub stars, experiments run directly in production, no upfront research. Everything was battle-tested the hard way — and that costs time and sanity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The team still pays the price.&lt;/strong&gt; After the MVP, the whole team has to sit down, read through the messy code, and redo the decomposition. The "fast start" delays the pain — it doesn't eliminate it.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;💬 &lt;em&gt;Sound familiar? Drop a comment — I'm curious how your team handles the post-MVP cleanup.&lt;/em&gt;&lt;br&gt;

&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;





&lt;div class="crayons-card c-embed"&gt;

  
&lt;h3&gt;
  
  
  ✅ Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business gets a proof of concept for relatively little money.&lt;/strong&gt; If the idea works, they invest properly and build a real product. If it doesn't — losses are minimal. It's essentially the Lean Startup model in practice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The team gets a project, investment, and — let's be honest — work.&lt;/strong&gt; In today's climate of automation anxiety, that last point matters more than people admit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We cleared almost all of our company's technical debt&lt;/strong&gt; using this approach. AI accelerated the start, but the people cleaned up and built what lasts.&lt;/li&gt;
&lt;/ul&gt;




&lt;/div&gt;


&lt;h2&gt;
  
  
  So, Will AI Replace Developers?
&lt;/h2&gt;

&lt;p&gt;Based on my experience: &lt;strong&gt;no — and here's why.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The number of products being built is going to grow. Which means the amount of work is going to grow. The cycle looks like this: AI helps spin up an MVP quickly, it gets validated, it gets launched — and then someone still has to build something that actually works at scale.&lt;/p&gt;

&lt;p&gt;Because AI is prone to mistakes when designing databases, choosing indexes, picking architectural patterns, and selecting the right tech stack. These aren't edge cases — they're the core of every serious product. In the end, developers — senior and junior alike — will come in and fix it, extend it, maintain it. &lt;strong&gt;AI creates work. It doesn't eliminate it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes, there's a market dip right now, and it will probably continue for a while. But my bet is that the economy will recalibrate — and we're heading toward a genuine boom in software development and technology.&lt;/p&gt;

&lt;p&gt;The developers who will thrive aren't the ones who refuse to use AI. They're the ones who know how to wield it — &lt;strong&gt;and how to clean up after it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AI code quality is poor, but it's fast enough to validate ideas&lt;/li&gt;
&lt;li&gt;Use vibe-coding for MVP → sell → then rebuild properly&lt;/li&gt;
&lt;li&gt;The "fast start" creates real overhead for real developers&lt;/li&gt;
&lt;li&gt;AI increases the number of products → increases demand for developers&lt;/li&gt;
&lt;li&gt;Learn to use AI as a tool, not a replacement&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Have you shipped a vibe-coded MVP? Did the cleanup take longer than the build? Drop a comment — let's compare war stories.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop Writing CRUD Boilerplate for FastAPI — Use ViewSets Instead</title>
      <dc:creator>Alexander Valenchits</dc:creator>
      <pubDate>Tue, 21 Apr 2026 09:34:37 +0000</pubDate>
      <link>https://dev.to/valenchits/stop-writing-crud-boilerplate-for-fastapi-use-viewsets-instead-54ek</link>
      <guid>https://dev.to/valenchits/stop-writing-crud-boilerplate-for-fastapi-use-viewsets-instead-54ek</guid>
      <description>&lt;p&gt;If you're building REST APIs with FastAPI, you've probably written this code dozens of times: a router, a list endpoint, a get-by-id endpoint, a create, an update, a delete… and then you do it all over again for the next resource. And the next. And the next.&lt;/p&gt;

&lt;p&gt;There's a better way. Meet &lt;strong&gt;fastapi-viewsets&lt;/strong&gt; — a library inspired by Django REST Framework's ViewSets that lets you generate all six standard CRUD endpoints from a single class with one method call.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: FastAPI CRUD Is Verbose by Design
&lt;/h2&gt;

&lt;p&gt;FastAPI is fantastic for building APIs. It's fast, it's type-safe, it generates OpenAPI docs automatically. But it's deliberately low-level — it gives you routing primitives, not conventions. That means every resource you build looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.orm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&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="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&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="n"&gt;limit&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="nd"&gt;@router.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;

&lt;span class="nd"&gt;@router.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;

&lt;span class="nd"&gt;@router.patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exclude_unset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;

&lt;span class="nd"&gt;@router.delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items/{id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;delete_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;get_db&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&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's ~50 lines of nearly identical logic. And you'll write it again for &lt;code&gt;users&lt;/code&gt;, &lt;code&gt;orders&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;categories&lt;/code&gt;... You get the idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: fastapi-viewsets
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fastapi-viewsets&lt;/code&gt; brings DRF-style ergonomics to FastAPI. Instead of repeating yourself, you declare one class, call &lt;code&gt;.register()&lt;/code&gt;, and all the endpoints are wired up automatically — complete with Pydantic validation, OpenAPI docs, and proper HTTP semantics.&lt;/p&gt;

&lt;p&gt;Here's the same resource from above, rewritten with the library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;String&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_viewsets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseViewset&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_viewsets.db_conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_session&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BaseViewset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;db_session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;strong&gt;~20 lines instead of ~50, and you get the same 5 endpoints.&lt;/strong&gt; The more resources you have, the more time you save.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Generated
&lt;/h2&gt;

&lt;p&gt;A single &lt;code&gt;.register()&lt;/code&gt; call produces these routes (using &lt;code&gt;LIST&lt;/code&gt;, &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all items (paginated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Get single item by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a new item&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PATCH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Partial update (only provided fields)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full replacement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DELETE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/items/{id}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete by ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Built-in &lt;strong&gt;pagination&lt;/strong&gt; is included on the LIST endpoint via &lt;code&gt;limit&lt;/code&gt; and &lt;code&gt;offset&lt;/code&gt; query parameters — no extra code needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /items?limit=20&amp;amp;offset=40
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Multi-ORM Support
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;fastapi-viewsets&lt;/code&gt; isn't locked to one ORM. As of v1.1.0, it ships with pluggable adapters for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SQLAlchemy&lt;/strong&gt; (sync and async)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tortoise ORM&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Peewee&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can select your ORM via the &lt;code&gt;ORM_TYPE&lt;/code&gt; environment variable, or pass an adapter instance directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"fastapi-viewsets[tortoise]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;BaseViewset&lt;/code&gt; / &lt;code&gt;AsyncBaseViewset&lt;/code&gt; interface works regardless of which adapter you're using.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async Support Out of the Box
&lt;/h2&gt;

&lt;p&gt;If your FastAPI app uses &lt;code&gt;async/await&lt;/code&gt; (and if you care about performance, it probably should), use &lt;code&gt;AsyncBaseViewset&lt;/code&gt; — it has the exact same interface but all handlers are &lt;code&gt;async&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi_viewsets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncBaseViewset&lt;/span&gt;

&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AsyncBaseViewset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;db_session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_async_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# your async session factory
&lt;/span&gt;    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include_router&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap &lt;code&gt;BaseViewset&lt;/code&gt; → &lt;code&gt;AsyncBaseViewset&lt;/code&gt; and &lt;code&gt;get_session&lt;/code&gt; → &lt;code&gt;get_async_session&lt;/code&gt;. Nothing else changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Authentication in One Line
&lt;/h2&gt;

&lt;p&gt;Want to protect write operations behind OAuth2? Pass your &lt;code&gt;OAuth2PasswordBearer&lt;/code&gt; and a list of methods that require a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.security&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OAuth2PasswordBearer&lt;/span&gt;

&lt;span class="n"&gt;oauth2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OAuth2PasswordBearer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BaseViewset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ItemSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;db_session&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LIST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;oauth_protect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;oauth2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;protected_methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PATCH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DELETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# read is public, writes require auth
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;LIST&lt;/code&gt; remain open. &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; require a bearer token. The Swagger UI will automatically show the lock icon on protected routes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Custom Logic: Just Subclass
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BaseViewset&lt;/code&gt; is an &lt;code&gt;APIRouter&lt;/code&gt; under the hood, so you can extend it just like any Python class. Override any handler method to add custom business logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ItemsWithStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseViewset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Add custom routes BEFORE register() to avoid path conflicts
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_api_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/stats&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;collection_stats&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collection_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resource&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&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="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Add custom filtering logic here
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're not locked into the generated behavior — it's a starting point, not a cage.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Compares
&lt;/h2&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;fastapi-viewsets&lt;/th&gt;
&lt;th&gt;fastapi-crudrouter&lt;/th&gt;
&lt;th&gt;Hand-rolled FastAPI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pattern&lt;/td&gt;
&lt;td&gt;DRF-style ViewSet class + .register()&lt;/td&gt;
&lt;td&gt;CRUD generator, router per resource&lt;/td&gt;
&lt;td&gt;Full manual control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boilerplate&lt;/td&gt;
&lt;td&gt;Minimal — one class + .register()&lt;/td&gt;
&lt;td&gt;Low — one router call per resource&lt;/td&gt;
&lt;td&gt;Maximum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM support&lt;/td&gt;
&lt;td&gt;SQLAlchemy, Tortoise, Peewee via adapters&lt;/td&gt;
&lt;td&gt;SQLAlchemy, Tortoise, Ormar, Databases, Gino, In-Memory&lt;/td&gt;
&lt;td&gt;Any ORM you integrate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async&lt;/td&gt;
&lt;td&gt;AsyncBaseViewset&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Fully custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;OAuth2 per-method via register()&lt;/td&gt;
&lt;td&gt;Custom dependencies&lt;/td&gt;
&lt;td&gt;Fully custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensibility&lt;/td&gt;
&lt;td&gt;Subclass + override any handler&lt;/td&gt;
&lt;td&gt;Limited override options&lt;/td&gt;
&lt;td&gt;Unlimited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pagination&lt;/td&gt;
&lt;td&gt;limit/offset built-in&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;td&gt;Fully custom&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fits existing routers&lt;/td&gt;
&lt;td&gt;Yes — BaseViewset is an APIRouter subclass&lt;/td&gt;
&lt;td&gt;Partial — requires specific integration&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 key difference from &lt;code&gt;fastapi-crudrouter&lt;/code&gt; is the &lt;strong&gt;DRF-style ViewSet pattern&lt;/strong&gt; — the &lt;code&gt;BaseViewset&lt;/code&gt; is a full &lt;code&gt;APIRouter&lt;/code&gt; subclass, so it fits naturally into any existing FastAPI app without any special integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;fastapi-viewsets

&lt;span class="c"&gt;# Optional ORM extras&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"fastapi-viewsets[sqlalchemy]"&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"fastapi-viewsets[tortoise]"&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"fastapi-viewsets[peewee]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The roadmap includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-side search&lt;/strong&gt; — wire the &lt;code&gt;search&lt;/code&gt; query param to real database queries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declarative ordering and advanced filters&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated &lt;code&gt;AsyncModelViewSet&lt;/code&gt;&lt;/strong&gt; ergonomics on top of SQLAlchemy 2.x async sessions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Richer OpenAPI&lt;/strong&gt; for nested Pydantic models&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The library is open source under the MIT license. If it saves you time, a ⭐ on GitHub goes a long way!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;PyPI&lt;/strong&gt;: &lt;a href="https://pypi.org/project/fastapi-viewsets/" rel="noopener noreferrer"&gt;https://pypi.org/project/fastapi-viewsets/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐙 &lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/svalench/fastapi_viewsets" rel="noopener noreferrer"&gt;https://github.com/svalench/fastapi_viewsets&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop writing the same CRUD code over and over. Your future self will thank you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by Alexander Valenchits — Tech Lead @ AluSoft, Minsk.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: &lt;code&gt;fastapi&lt;/code&gt; &lt;code&gt;python&lt;/code&gt; &lt;code&gt;webdev&lt;/code&gt; &lt;code&gt;tutorial&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
