There are a variety of languages that are suitable for backend development, and any would be a fine choice for your app. In this book, I’ve chosen to use Python as I find that the code is more accessible and easier to follow than other languages.
As we will be writing the backend for our app in Python, we will need to have it installed locally. While you may have a Python version already installed, I’d recommend you use the one installed by the system package manager, as follows:
brew install python
scoop install python
The package manager we’ve used so far doesn’t know how to install and manage Python packages, so we also need another package manager. There are many choices in Python, and I think PDM is the best. PDM can be installed with the system package manager on Linux and macOS systems, as follows:
brew install pdm
For Windows systems, it can be installed by running the following commands:
scoop bucket add frostming https://github.com/frostming/scoop-frostming.git
scoop install pdm
We’ll keep the backend code separate in a backend folder, so please create a backend folder at the top level of the project with the following folder structure:
tozo
└── backend
├── src
│ └── backend
└── tests
Next, we need to inform git that there are files that we don’t want to be tracked in the repository and hence it should ignore them by adding the following to backend/.gitignore:
/__pypackages__
/.mypy_cache
.pdm.toml
.pytest_cache
.venv
*.pyc
For PDM to manage our project, we need to run the following command in the backend directory:
pdm init
When prompted, you should choose the Python version installed using the system package manager earlier.
We can now focus on the specific Python tooling for fast development.
Formatting the code
Python does not have an official format or formatter; however, black
is the de facto formatter for code and isort
is the de facto formatter for imports. We can add both to our project by running the following command in the backend directory:
pdm add --dev black isort
The dev flag
We use the --dev
flag here as these tools are only required for developing the backend and therefore do not need to be installed when running in production.
black
and isort
require the following configuration to work well together. This should be added to the end of the backend/pyproject.toml file (you may have to change the target-version
if you are using a version of Python other than 3.10) as follows:
[tool.black]
target-version = ["py310"]
[tool.isort]
profile = "black"
The following commands will run black
and isort
on our code in the src and tests folders:
pdm run black src tests
pdm run isort src tests
We’ll be using Jinja templates for emails sent by our app. While these templates are code, they are not Python and hence require a different formatter. Thankfully, djhtml
can be used to format the templates and is added by running the following command in the backend folder:
pdm add --dev djhtml
The following command will run djhtml
on our template code:
djhtml src/backend/templates --tabwidth 2 --check
We’ve now installed the tooling we need to format the code in the backend. Next, we can install the tooling we need to lint the code.
Linting the code
Python supports type hints that describe the expected types of variables, functions, and so on. We’ll use type hints and tooling to check that we haven’t introduced any type-related bugs. The most popular type checking tool for Python is mypy
. It is installed by running the following command in the backend directory:
pdm add --dev mypy
The following command will run mypy
over the backend code:
pdm run mypy src/backend/ tests/
With mypy
helping us find type errors, we can add Flake8
to help us find other bugs. Flake8
is installed with pdm
as follows:
pdm add --dev flake8
Flake8
must be configured to work with black
and mypy
by adding the following to backend/setup.cfg:
[flake8]
max-line-length = 88
extend-ignore = E203
Flake8
is used by running the following command:
pdm run flake8 src/ tests/
There is another type of bug that we can use tooling to help us find, and these are related to security. A good example would be checking for a SQL injection vulnerability. Bandit is another linter that helps identify these bugs, and it is installed by running the following command in the backend directory:
pdm add --dev bandit
Bandit only needs to lint the src
code as the test code does not run during production. To run Bandit over the src
code, the following command is used:
pdm run bandit -r src/
Bandit ModuleNotFoundErrors
Bandit may fail to run with the error ModuleNotFoundError: No module named ‘pkg_resources’
. If this happens, then run pdm add --dev setuptools
to add the missing module.
We now have tooling looking for bugs, but we can also add tooling to look for unused code. This is helpful as code can often be forgotten during refactoring, leaving files that are much more complex to read and understand than they should be. I like to use vulture
to find unused code, and it is installed by running the following command in the backend directory:
pdm add --dev vulture
Unfortunately, vulture
can report false positives, so I like to configure it to be 100% confident when reporting issues by adding the following configuration to backend/pyproject.toml:
[tool.vulture]
min_confidence = 100
Like Bandit, it is best to run vulture
over the src
code only (not the tests) via the following command:
pdm run vulture src/
Now, let’s look at what we need to test the code.
Testing the code
Python has unittest
as part of its standard library, however, I think using pytest
is superior. pytest
is very feature-rich and allows for very simple and clear tests, such as the following small example that tests that a simple addition is correct:
def test_addition():
assert 1 + 1 == 2
pytest
requires the pytest-asyncio
plugin to test async code, and they are both installed with pdm
as follows:
pdm add --dev pytest pytest-asyncio
pytest
is best configured to show local variables on test failure as this makes it much easier to understand why the test is failing. In addition, the asyncio
mode should be set to auto
to make writing async tests easier. The following configuration should be placed in backend/pyproject.toml:
[tool.pytest.ini_options]
addopts = "--showlocals"
asyncio_mode = "auto"
pythonpath = ["src"]
To run the tests, pytest
is invoked with the tests
path as follows:
pdm run pytest tests
Now that we’ve installed all of the tooling, we need some simple commands to run it.
Scripting the commands
We’ve added a lot of useful tooling to our project; however, each one had a different unique command that we’d have to remember. This is something we can simplify by making use of PDM’s scripting feature as it can be used to map PDM commands to the required commands. We will add the following three PDM scripting commands:
pdm run format
to run the formatting tooling and format the code
pdm run lint
to run the linting tooling and lint the code
pdm run test
to run the tests
PDM’s scripting requires these script commands to be added to the backend/pyproject.toml file as follows:
[tool.pdm.scripts]
format-black = "black src/ tests/"
format-djhtml = "djhtml src/backend/templates -t 2 --in-place"
format-isort = "isort src tests"
format = {composite = ["format-black", "format-djhtml", "format-isort"]}
lint-bandit = "bandit -r src/"
lint-black = "black --check --diff src/ tests/"
lint-djhtml = "djhtml src/backend/templates -t 2 --check"
lint-flake8 = "flake8 src/ tests/"
lint-isort = "isort --check --diff src tests"
lint-mypy = "mypy src/backend tests"
lint-vulture = "vulture src"
lint = {composite = ["lint-bandit", "lint-black", "lint-djhtml", "lint-flake8", "lint-isort", "lint-mypy", "lint-vulture"]}
test = "pytest tests/"
With the backend tooling in place and accessible via easy-to-remember commands, we can now do the same for the frontend.