django-project-structure/docs/index.html

811 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="This is a template/project structure for developing django-based applications - either strictly through the django-rest-framework or just django.">
<meta name="author" content="Saqibur Rahman">
<link rel="canonical" href="https://github.com/saqibur/django-project-structure/">
<link rel="icon" href="assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.5.3, mkdocs-material-9.4.12">
<title>Django Project Structure</title>
<link rel="stylesheet" href="assets/stylesheets/main.fad675c6.min.css">
<link rel="stylesheet" href="assets/stylesheets/palette.356b1318.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL(".",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="indigo" data-md-color-accent="indigo">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#django-project-structure" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="." title="Django Project Structure" class="md-header__button md-logo" aria-label="Django Project Structure" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
Django Project Structure
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Home
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://github.com/saqibur/django-project-structure" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
saqibur/django-project-structure
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="." title="Django Project Structure" class="md-nav__button md-logo" aria-label="Django Project Structure" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
Django Project Structure
</label>
<div class="md-nav__source">
<a href="https://github.com/saqibur/django-project-structure" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M439.55 236.05 244 40.45a28.87 28.87 0 0 0-40.81 0l-40.66 40.63 51.52 51.52c27.06-9.14 52.68 16.77 43.39 43.68l49.66 49.66c34.23-11.8 61.18 31 35.47 56.69-26.49 26.49-70.21-2.87-56-37.34L240.22 199v121.85c25.3 12.54 22.26 41.85 9.08 55a34.34 34.34 0 0 1-48.55 0c-17.57-17.6-11.07-46.91 11.25-56v-123c-20.8-8.51-24.6-30.74-18.64-45L142.57 101 8.45 235.14a28.86 28.86 0 0 0 0 40.81l195.61 195.6a28.86 28.86 0 0 0 40.8 0l194.69-194.69a28.86 28.86 0 0 0 0-40.81z"/></svg>
</div>
<div class="md-source__repository">
saqibur/django-project-structure
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Home
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="." class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Home
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#getting-started" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<nav class="md-nav" aria-label="Getting Started">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-an-app" class="md-nav__link">
<span class="md-ellipsis">
Creating an App
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#project-tree" class="md-nav__link">
<span class="md-ellipsis">
Project Tree
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rationale" class="md-nav__link">
<span class="md-ellipsis">
Rationale
</span>
</a>
<nav class="md-nav" aria-label="Rationale">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#apps-folder" class="md-nav__link">
<span class="md-ellipsis">
apps Folder
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#services" class="md-nav__link">
<span class="md-ellipsis">
services
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api-folder" class="md-nav__link">
<span class="md-ellipsis">
api Folder
</span>
</a>
<nav class="md-nav" aria-label="api Folder">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#api-versioning" class="md-nav__link">
<span class="md-ellipsis">
API Versioning
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-v2-of-an-api" class="md-nav__link">
<span class="md-ellipsis">
What's v2 of an API?
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#config" class="md-nav__link">
<span class="md-ellipsis">
config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#deployments" class="md-nav__link">
<span class="md-ellipsis">
deployments
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#exception-handling" class="md-nav__link">
<span class="md-ellipsis">
Exception handling
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#references" class="md-nav__link">
<span class="md-ellipsis">
References
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#getting-started" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
<nav class="md-nav" aria-label="Getting Started">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#creating-an-app" class="md-nav__link">
<span class="md-ellipsis">
Creating an App
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#project-tree" class="md-nav__link">
<span class="md-ellipsis">
Project Tree
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#rationale" class="md-nav__link">
<span class="md-ellipsis">
Rationale
</span>
</a>
<nav class="md-nav" aria-label="Rationale">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#apps-folder" class="md-nav__link">
<span class="md-ellipsis">
apps Folder
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#services" class="md-nav__link">
<span class="md-ellipsis">
services
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#api-folder" class="md-nav__link">
<span class="md-ellipsis">
api Folder
</span>
</a>
<nav class="md-nav" aria-label="api Folder">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#api-versioning" class="md-nav__link">
<span class="md-ellipsis">
API Versioning
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#whats-v2-of-an-api" class="md-nav__link">
<span class="md-ellipsis">
What's v2 of an API?
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#config" class="md-nav__link">
<span class="md-ellipsis">
config
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#deployments" class="md-nav__link">
<span class="md-ellipsis">
deployments
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#exception-handling" class="md-nav__link">
<span class="md-ellipsis">
Exception handling
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#references" class="md-nav__link">
<span class="md-ellipsis">
References
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<p><a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg" /></a>
<a href="https://www.python.org/downloads/release/python-3108//"><img alt="Python 3.10.8" src="https://img.shields.io/badge/python-3.10.8-blue.svg" /></a>
<a href="https://github.com/astral-sh/ruff"><img alt="Ruff" src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" /></a></p>
<p><img alt="Django" src="https://img.shields.io/badge/django-%23092E20.svg?style=for-the-badge&amp;logo=django&amp;logoColor=white" />
<img alt="DjangoREST" src="https://img.shields.io/badge/DJANGO-REST-ff1709?style=for-the-badge&amp;logo=django&amp;logoColor=white&amp;color=ff1709&amp;labelColor=gray" />
<img alt="Postgres" src="https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&amp;logo=postgresql&amp;logoColor=white" />
<img alt="Swagger" src="https://img.shields.io/badge/-Swagger-%23Clojure?style=for-the-badge&amp;logo=swagger&amp;logoColor=white" /></p>
<h1 id="django-project-structure">Django Project Structure</h1>
<p>This is a template/project structure for developing django-based applications -
either strictly through the <code>django-rest-framework</code> or just <code>django</code>.</p>
<p>The project is meant to be easily clone-able, and used as the starter template
for the next big thing you develop. Note, this is a folder structure only, not
“best practices”.</p>
<h2 id="getting-started">Getting Started</h2>
<ol>
<li>Since this is a template repository, simply hit "Use this template" on GitHub
and follow the instructions. Otherwise, you can just clone the repo, remove/add
anything you see fit.</li>
<li>Run the project using <code>python manage.py runserver</code> and you should see the
default success page provided by Django at
<a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>.</li>
</ol>
<h3 id="creating-an-app">Creating an App</h3>
<ol>
<li>Create a folder with the app name in <code>apps</code>. For example: <code>poll</code></li>
<li>Run <code>python manage.py startapp poll apps/poll</code> from the root directory of the
project</li>
</ol>
<h2 id="project-tree">Project Tree</h2>
<pre><code class="language-bash">.
├── apps
│ └── example # A django rest app
│ ├── api
│ │ ├── v1 # Only the &quot;presentation&quot; layer exists here.
│ │ │ ├── __init__.py
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ └── views.py
│ │ ├── v2 # Only the &quot;presentation&quot; layer exists here.
│ │ │ ├── __init__.py
│ │ │ ├── serializers.py
│ │ │ ├── urls.py
│ │ │ └── views.py
│ │ └── __init__.py
│ ├── fixtures # Constant &quot;seeders&quot; to populate your database
│ ├── management
│ │ ├── commands # Try and write some database seeders here
│ │ │ └── command.py
│ │ └── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── templates # App-specific templates go here
│ ├── tests # All your integration and unit tests for an app go here.
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── models.py
│ ├── services.py # Your business logic and data abstractions go here.
│ ├── urls.py
│ └── views.py
├── common # An optional folder containing common &quot;stuff&quot; for the entire project
├── config
│ ├── settings.py
│ ├── asgi.py
│ ├── __init__.py
│ ├── urls.py
│ └── wsgi.py
├── deployments # Isolate Dockerfiles and docker-compose files here.
├── docs
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING.md
│ ├── deployment.md
│ ├── local-development.md
│ └── swagger.yaml
├── requirements
│ ├── common.txt # Same for all environments
│ ├── development.txt # Only for a development server
│ ├── local.txt # Only for a local server (example: docs, performance testing, etc.)
│ └── production.txt # Production only
├── static # Your static files
├── .env.example # An example of your .env configurations. Add necessary comments.
├── static # Your static files
├── .gitignore # https://github.com/github/gitignore/blob/main/Python.gitignore
├── entrypoint.sh # Any bootstrapping necessary for your application
├── manage.py
├── pytest.ini
└── README.md
</code></pre>
<h2 id="rationale">Rationale</h2>
<p>Each <code>app</code> should be designed in way to be plug-able, that is, dragged and dropped
into any other project and itll work independently.</p>
<h3 id="apps-folder"><code>apps</code> Folder</h3>
<ul>
<li>A mother-folder containing all apps for our project. Congruent to any
JS-framework's <code>src</code> folder. If you really wanted to, you could even call it the
<code>src</code> folder. Again, it's up to you.</li>
<li>An app can be a django template project, or an rest framework API.</li>
</ul>
<h3 id="services"><code>services</code></h3>
<ul>
<li>Well be writing business logic in services instead of anywhere else.</li>
<li>There's a common argument: "Why not just use model managers?", and honestly,
that's a fair point. However, for our use case, we've often noticed that a single
service can leverage more zero to many models. Either way, managers or services,
both work towards the same goal - isolating business logic away from views, and
brings it closer to the data.</li>
</ul>
<h3 id="api-folder"><code>api</code> Folder</h3>
<ul>
<li>We like to place all our API components into a package within an app called
<code>api</code>. For example, in this repository it's the <code>example/api</code> folder. That
allows us to isolate our API components in a consistent location. If
we were to put it in the root of our app, then we would end up with a huge list
of API-specific modules in the general area of the app. That's without getting
into the mess of API versioning.</li>
</ul>
<p>For projects with a lot of small, interconnecting apps, it can be hard to hunt
down where a particular API view lives. In contrast to placing all API code
within each relevant app, sometimes it makes more sense to build an app
specifically for the API. This is where all the serializers, renderers, and views
are placed. Therefore, the name of the app should reflect its API version</p>
<h4 id="api-versioning">API Versioning</h4>
<p>It might often be necessary to support multiple versions of an API throughout
the lifetime of a project. Therefore, we're adding in support right from the
start.</p>
<p>For different API versions, we're assuming the following will change:
- Serializers: That is, how the data is presented to a consumer
- Views: That is, how the data is accessed and modified by a consumer
- URLs: That is, where the consumer access the data</p>
<p><code>model</code>s and <code>service</code>s can be thought of as shared between versions. Therefore,
migrating changes should be versioned carefully without breaking different
versions of the API. After all, your API version is simply a presentation of how
data is handled and managed within your application.</p>
<p>Sufficient unit tests and integration tests should wrap services and API
endpoints to ensure full compatibility.</p>
<h4 id="whats-v2-of-an-api">What's <code>v2</code> of an API?</h4>
<p>Currently we're proposing that major changes to the following, constitutes a new API version:
1. Representation of data, either for submission or retrieval
1. Major optimizations
1. Major code reorganization and code refactor
1. Usually, in a Django project, you won't need to worry about API versioning</p>
<h3 id="config"><code>config</code></h3>
<ul>
<li>Contains project configuration files, including the primary URL file</li>
<li>~~Contains settings split into <code>base</code>, <code>local</code>, <code>production</code> and <code>development</code>.~~.
Update: As environment specific variables will be handled using environment
variables, we've deemed it unnecessary to have separate settings files for now.</li>
</ul>
<h3 id="deployments"><code>deployments</code></h3>
<ul>
<li>Contains Docker, Docker-Compose and nginx specific files for deploying in
different environments.</li>
</ul>
<h3 id="exception-handling">Exception handling</h3>
<p>You should probably add a custom exception handler to your project based on
who consumes your APIs. To learn how to create a custom exception handler,
you can check out the Django Rest Framework documentation at:
https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.feldroy.com/books/two-scoops-of-django-3-x">Two Scoops of Django by Daniel and Audrey Feldroy</a></li>
<li><a href="https://django-best-practices.readthedocs.io/en/latest/index.html">Django Best Practices</a></li>
<li><a href="https://github.com/cookiecutter/cookiecutter-django">Cookiecutter Django</a></li>
<li><a href="https://github.com/HackSoftware/Django-Styleguide">HackSoft Django Style Guide</a></li>
<li><a href="https://www.youtube.com/watch?v=yG3ZdxBb1oo">Radoslav Georgiev - Django Structure for Scale and Longevity</a></li>
<li><a href="https://apisyouwonthate.com/books/build-apis-you-wont-hate/">Build APIs You Won't Hate</a></li>
<li><a href="https://github.com/saqibur/tuxedo">Tuxedo Style Guides</a></li>
</ul>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
<div class="md-copyright__highlight">
Copyright &copy; 2022 - 2023 Saqibur Rahman
</div>
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": ".", "features": [], "search": "assets/javascripts/workers/search.f886a092.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="assets/javascripts/bundle.6c14ae12.min.js"></script>
</body>
</html>