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.
- Sign into your Neon account, after which you’ll be redirected to the Neon console.
- On the console, a project creation dialog will appear. This dialog allows us to specify the following:
- project name
- Postgres version
- database name
- region
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.
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
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
After activating the environment, install Django using the following command:
pip install django
Next, we create a new Django project django_neon
using
django-admin startproject django_neon
Then, navigate to the django_neon
directory and create a todos
app:
cd django_neon
django-admin startapp todos
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
]
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
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'},
}
}
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()
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 typemodels.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
Now, let’s confirm that our table and column have been created by selecting the Tables
section in the Neon console:
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/')
In the code snippet above, we created three functions:
-
task_lists
renders a webpage that displays a list of to-do tasks. It queries all the tasks from theTodo
model and passes them to thetasks.html
template using therender
function. -
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. -
delete_task
deletes a task from the to-do list based on thetodo_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>
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'))
]
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')
]
Let’s test this out by starting the server with the following command:
python manage.py runserver
Our application should look like this.
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.