Building scalable apps with serverless PostgreSQL & Django

Shuaib Oseni - Feb 8 - - Dev Community

The developer ecosystem is seeing a surge in the use of serverless architectures for building modern applications. Particularly noteworthy is the emergence of serverless PostgreSQL databases — a managed database service designed for serverless computing environments, offering automatic scaling, pay-as-you-go pricing, flexibility, and reduced operational overhead for developers.

In this article, we’ll be taking a look at Neon, an open-source, fully managed serverless Postgres. On top of traditional Postgres, Neon offers a range of benefits, including autoscaling to match varying workloads and git-like database branching, which allows developers to create a writeable isolated version of their original data and schema.

By the end of this tutorial, you'll have a solid understanding of how to use serverless PostgreSQL to create scalable and efficient solutions for modern web development, including building a to-do app with Neon's Postgres database and Django.

Github
Check out the complete source code here.

Prerequisites

To follow along with this tutorial, you’ll need the following:

  • Python >= 3.8.3 installed.
  • A basic understanding of Python and Django.
  • A Neon cloud account. Sign up here!

Creating a Neon project

To create our first Neon project, complete the following steps.

  1. Sign into your Neon account, after which you’ll be redirected to the Neon console.
  2. On the console, a project creation dialog will appear. This dialog allows us to specify the following:
    1. project name
    2. Postgres version
    3. database name
    4. region Creating a new project and database on Neon

After creating a project, we’ll be redirected to the Neon console, and a pop-up modal with a connection string will appear.

Neon also provides database connection settings for different programming languages and frameworks. We can retrieve the Django connection string by accessing the Connection Details widget on the Neon Dashboard and choosing Django from the dropdown menu.

Neon database connection details

Note: Copy this database connection string to a safe location; we’ll need it later in the tutorial!

Scaffolding a Django app

To start the process, let's create a new python virtual environment. This lets us use separate library installations for a project without installing them globally.

Now, let’s create a virtual environment by running the command below:

python -m venv env
Enter fullscreen mode Exit fullscreen mode

The command above tells Python to create the virtual environment in a directory named env in the current directory. On creation, we’ll activate the virtual environment using the following command:

source env/bin/activate
Enter fullscreen mode Exit fullscreen mode

After activating the environment, install Django using the following command:

pip install django
Enter fullscreen mode Exit fullscreen mode

Next, we create a new Django project django_neon using

django-admin startproject django_neon
Enter fullscreen mode Exit fullscreen mode

Then, navigate to the django_neon directory and create a todos app:

cd django_neon
django-admin startapp todos
Enter fullscreen mode Exit fullscreen mode

Let’s add our app to the list of already installed apps. Including an application in the INSTALLED_APPS list makes its functionality available to the project, allowing us to use its models, views, templates, and other components.

In our editor, navigate to the django_neon directory and edit the settings.py file:

        INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'todos' #our newly installed app
    ]
Enter fullscreen mode Exit fullscreen mode

Installing dependencies

A few other things to take care of: we need to install psycopg2, a PostgreSQL adapter for Python, in our Django app. This will enable Django to connect to our PostgreSQL database and provide the functionality to execute SQL queries and commands from our Django application to the PostgreSQL database.

To do so, run the following command in your terminal:

pip install psycopg2
Enter fullscreen mode Exit fullscreen mode

Setting up Neon credentials

To configure our application to use the Neon PostgreSQL database, we need to update our app's settings. Remember the Django connection settings we copied earlier? We’ll now update our settings.py file with those:

django_neon/settings.py

  DATABASES = {
      'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'tododb',
        'USER': 'wales149',
        'PASSWORD': 'YOUR_PASSWORD',
        'HOST': 'ep-purple-smoke-78175257.us-east-2.aws.neon.tech',
        'PORT': '5432',
        'OPTIONS': {'sslmode': 'require'},
      }
    }
Enter fullscreen mode Exit fullscreen mode

Keep in mind, while we’re putting the credentials directly into the file for simplicity here, a production app should keep sensitive information out of the codebase using environment variables. Check out this guide on creating environment variables in Django.

Creating a model class

Let’s update our model.py file, one of Django's most important files that helps define our data structure. This file is added by default when a project is created.

todos/model.py

 from django.db import models

    class Todo(models.Model):
        task = models.TextField()
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we:

  • define a Django model named Todo, which represents a to-do item in the application.
  • create a new class named Todo that inherits from models.Model.
  • define a single field within the Todo model. The field is named task, and it is of type models.TextField(). This indicates that the task field will store text data.

Next, we need to migrate our model to the database. Do so by running the following command:

  python manage.py makemigrations   #migrating the app and database changes
  python manage.py migrate          #final migrations
Enter fullscreen mode Exit fullscreen mode

Now, let’s confirm that our table and column have been created by selecting the Tables section in the Neon console:

Our newly created table

Creating a view

In our views.py file, which handles user requests and rendering responses, let’s add the following code:

todos/view.py

    from django.shortcuts import render, redirect
    from django.http import HttpResponse,HttpRequest
    from .models import Todo

    def task_lists(request):
        context = {'todo_list':Todo.objects.all()}
        return render(request,'todos/tasks.html', context)
    def add_task(request:HttpRequest):
        todo = Todo(task = request.POST['tasks'])
        todo.save()
        return redirect('/todos/list/')
    def delete_task(request, todo_id):
        task_to_delete = Todo.objects.get(id=todo_id)
        task_to_delete.delete()
        return redirect('/todos/list/')
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, we created three functions:

  1. task_lists renders a webpage that displays a list of to-do tasks. It queries all the tasks from the Todo model and passes them to the tasks.html template using the render function.
  2. add_task adds a new task to the to-do list. It extracts the task from the POST request, creates a new Todo object, saves it to the database, and then redirects the user to the /todos/list URL.
  3. delete_task deletes a task from the to-do list based on the todo_id. It retrieves the corresponding Todo object from the database, deletes it, and redirects the user to the /todos/list/ URL.

Creating templates

We start by creating a templates directory in our todos directory. We create an index.html file and add the following in our newly created directory:

todos/templates/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Neon App</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" integrity="sha512-YWzhKL2whUzgiheMoBFwW8CKV4qpHQAEuvilg9FAn5VJUDwKZZxkJNuGM4XkWuk94WCrrwslk8yWNGmY1EduTA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    </head>
    <body style="background-color: #2e2e2e;">
        <div class="container">
            <div class="row mt-5">
                <div class="col-md-8 offset-md-2">
                    <div class="card">
                        <div class="card-header shadow-sm" style="background-color: #B10F49;">
                            <h1 class="display-5 text-info">
                                <i class="fa-solid fa-list-check text-light">My List</i>
                            </h1>
                        </div>
                        <div class="card body">
                            <ul class="list-group">
                                <li class="list-group-item">
                                    <form action="{% url 'add_task' %}" method="post">
                                        {% csrf_token %}
                                        <div class="input-group">
                                            <input type="text" class="form-control" name="tasks">
                                            <div class="input-group-append text-info">
                                                <span class="input-group-text bg-white py-0">
                                                    <button type="submit" class="btn btn-sm text-info">
                                                        <i class="fa-regular fa-square-plus fa-lg text-success"></i>
                                                    </button>
                                                </span>
                                            </div>
                                        </div> 
                                    </form>
                                </li>
                                {% for todo in todo_list %}
                                <li class="list-group-item">{{todo.task}}</li>
                                <form action="{% url 'delete_task' todo.id %}" method="post" class="float-right d-inline">
                                    {% csrf_token %}
                                    <button type="submit" class="btn">
                                        <i class="fa-solid fa-calendar-xmark text-danger float-right"></i>
                                    </button>
                                </form>
                                {% endfor %}
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>


    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
    </body>
    </html>
Enter fullscreen mode Exit fullscreen mode

In the HTML snippet above, we did the following:

  • display each task as a list item <li> within the list group
  • use a for loop {% for todo in todo_list %} to iterate over the tasks from the database and display them
  • add a delete button for each task
  • use {% url 'add_task' %} and {% url 'delete_task' todo.id %} as placeholders that will be replaced with the actual URLs by Django during rendering

Now, let’s configure routing for our app by updating the urls.py file in our django_neon directory. This file helps define the URL patterns for our Django project.

    from django.contrib import admin
    from django.urls import path, include
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('todos/', include('todos.urls'))
    ]
Enter fullscreen mode Exit fullscreen mode

Then, in our **todos** directory, let’s create a separate **urls.py** file, distinct from the default one. Add the following code to this new **urls.py** file:

    from django.urls import path 
    from . import views
    urlpatterns = [
        path('list/',views.task_lists),
        path('add_task/',views.add_task, name='add_task'),
        path('delete_task/<int:todo_id>/',views.delete_task, name='delete_task')
    ]
Enter fullscreen mode Exit fullscreen mode

Let’s test this out by starting the server with the following command:

python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Our application should look like this.

Django Neon | Opentape

Oseni Shuaib - Feb 7th, 4:37pm

favicon app.opentape.io

Conclusion

In this tutorial, we demonstrated how to use Django backed by serverless Postgres (Neon) by building a simple todo application. We also went through the essential steps, from setting up the Django environment and defining models to seamlessly integrating Neon.

Resources

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .