Server-Side Dart with PostgreSQL

How to build cloud apps in Dart using dotenv, shelf, and postgres

Kenneth Reilly
The Startup

--

Introduction

In the 21st century, modern web applications are expected to be lean, perform well, and be easy to maintain. Concepts like DevOps and twelve-factor app architecture are becoming increasingly popular, and the ability to quickly build and deploy web services to spec is the primary advantage of employing those kinds of strategies and methodologies.

In this article we’ll look at the process of building a simple API endpoint to service a basic PostgreSQL DB for two common CRUD tasks: creating some arbitrary item, and reading a list of items that have been created.

The source code for this project is available on GitHub here. To run the project, or to create one like it, the following tools are required:

PostgreSQL Database Setup

Since the objective is to save and retrieve data from a PostgreSQL database, the first thing we’ll need to do is create one. With PostgreSQL installed and running on your environment, execute the following commands (which are also located in create_db.sh) to create the database:

$ psql postgres -c "CREATE USER dart_pg_user WITH LOGIN SUPERUSER INHERIT CREATEDB CREATEROLE NOREPLICATION PASSWORD 'insecure_password'"$ psql postgres -c "CREATE DATABASE dart_pg_example WITH OWNER = dart_pg_user ENCODING = 'UTF8' CONNECTION LIMIT = -1;"$ psql dart_pg_example -f db/items-table.sql

The first two commands create the database userdart_pg_user and the database dart_pg_example respectively. The third runs the items-table.sql file which creates an items table with the fields id, name, description, and create_timestamp. For convenience and simplicity, only a value for name is required when inserting new rows, since id is auto-generated, description allows null, and create_timestamp defaults to current_timestamp.

Feel free to change insecure_password to anything else, but make sure to change it also in the .env.example file to avoid any connection issues later.

Dart Project Setup

For this demo project, I ran the command $ stagehand package-shelf which created the project structure and some boilerplate for a basic shelf web server.

The main project configuration file pubspec.yaml outlines the basic info and package requirements for the application:

Included are the shelf, dotenv, and postgres packages. Shelf provides a simple way to handle middleware and perform other functions typical of a web server, while the dotenv package enables the app to load variables both from the local environment and a .env file. For this demo project, a default config file .env.example has been provided and is used by the app:

This file specifies the web server port 9001 along with the port, username, and so forth for the DB connection. Pulling config information from the environment, and utilizing local .env files to simulate that environment when developing locally, is a cornerstone of twelve-factor app design and allows for rapid deployment to modern services like Heroku.

Application Entry Point

Let’s take a look at the main entry point for this app, bin/server.dart:

The Server class performs setup of the underlying API and DB layers and handles incoming HTTP requests. There should only ever be one copy of this class in memory, so it’s declared as abstract (instances of it can’t be created and all properties and methods must be declared static).

JsonDecoder and JsonEncoder are imported from dart:convert and instances of them are created, which are used to process JSON request and response data with the _decode and _encode methods. This allows the Server class to handle all of the JSON processing locally while interfacing downstream to the underlying architecture using plain Map objects.

The _get and _post methods handle GET and POST requests, which are forwarded down from the _handle method that processes all incoming HTTP requests. There are two endpoints served by this application:

HTTP GET /items (returns all rows from the items table)

HTTP POST /item (inserts row from parsed JSON into the items table)

Finally within the Server class there is the start method which loads the environment vars, initializes the API layer, creates a pipeline handler that wraps the _handle method described above, and then runs the shelf server.

The API Class

Next up is the simple API layer for this application, lib/api.dart:

The API class is also abstract, since one copy in memory is enough for this simple application. In a more complex scenario, several instances may be required, for example to handle different API version levels or to provide better service to customers based on geolocation or timezone data.

The _db property holds an instance of the DB class, which is created when the init method is called (by the Server). The two endpoints exposed by the server are implemented here as index and addItem. The index method runs a query on the items table and constructs a more efficient response object from the query result, returning the new object when complete. The addItem method creates an instance of Item from a factory method and inserts it into the items table, returning the newly created item’s id as a result.

The Item Class

Let’s check out the data model for an item, defined in lib/models/item.dart:

The Item class has properties for name and description, a constructor that requires a value for name, and the fromDynamicMap method, which returns an Item with name and description retrieved from the map object.

The DB Class

The last (and lowest-level) class in this project is the Database in lib/db.dart:

The Database class provides the factory method connect, which takes a map of environment variables and creates the actual connection to the database using the provided configuration data. The other method in this class, query, simply proxies the sql and query values to the PostgreSQL connection via mappedResultsQuery, which returns the query result as a map instead of raw values (making it more suitable for a meaningful JSON server response).

Testing it Out

To start the server, run$ dart bin/server.dart and the message Serving at http://localhost:9001 should appear to indicate the server is up-and-running at that location. To create a test item in the database:

$ curl -X POST localhost:9001/item -d '{"name":"test"}'

The response from the server should be something like {"id":1} which indicates that an item was created with an id of 1. To retrieve a list of records from the items table:

$ curl localhost:9001/items

This will return a JSON serialized array like [{"id":1,"name":"test"}] that contains a list of previously added items.

Conclusion

This example demonstrates how to create REST APIs and services with Dart and PostgreSQL that are easy to organize, deploy, and scale to meet demand. The powerful and rich feature set of both of these technologies make them an excellent combination for durable high-performance apps and services.

Thanks for reading this article and good luck with your next DevOps project!

Kenneth Reilly (8_bit_hacker) is CTO of LevelUP

--

--