Build and deploy a REST API with Postgres database in TypeScript
Marcus Kohlberg
Posted on April 15, 2024
In this tutorial you will create a REST API for a URL Shortener service using Encore for TypeScript, a new way of building fully type-safe and production-ready distributed systems in TypeScript using declarative infrastructure.
With Encore you can declare infrastructure like databases, Pub/Sub, cron jobs, and caches, directly in your application code. Encore takes care of running your local environment, instruments your app with tracing, and automatically provisions and deploys to your cloud in AWS/GCP.
In a few short minutes, you'll learn how to:
- Create REST APIs with Encore
- Use PostgreSQL databases
- Create and run tests
- Deploy to the cloud
💽 Install Encore
Install the Encore CLI to run your local environment:
-
macOS:
brew install encoredev/tap/encore
-
Linux:
curl -L https://encore.dev/install.sh | bash
-
Windows:
iwr https://encore.dev/install.ps1 | iex
Create a service and endpoint
Create a new application by running encore app create
and select Empty app
as the template.
Now let's create a new url
service.
In your application's root folder, create a new folder url
and create a new file url.ts
that looks like this:
import { api } from "encore.dev/api";
import { randomBytes } from "node:crypto";
interface URL {
id: string; // short-form URL id
url: string; // complete URL, in long form
}
interface ShortenParams {
url: string; // the URL to shorten
}
// Shortens a URL.
export const shorten = api(
{ method: "POST", path: "/url", expose: true },
async ({ url }: ShortenParams): Promise<URL> => {
const id = randomBytes(6).toString("base64url");
return { id, url };
},
);
This sets up the POST /url
endpoint.
Let’s see if it works! Start your app by running encore run
.
You should see this:
Encore development server running!
Your API is running at: http://127.0.0.1:4000
Development Dashboard URL: http://localhost:9400/5g288
3:50PM INF registered API endpoint endpoint=shorten path=/url service=url
💡 You can now open your local development dashboard at: http://localhost:9400
In the dashboard you will find an API explorer, complete API documentation and a Service Catalog, and distributed tracing.
🕹 Try it out
Next, call your endpoint:
$ curl http://localhost:4000/url -d '{"url": "https://encore.dev"}'
You should see this:
{
"id": "5cJpBVRp",
"url": "https://encore.dev"
}
It works!
You can now check out a trace of your request in the local development dashboard, showing you exactly what happened when you made the request.
It will look something like this example:
Save URLs in a database
So the API works, but there's just one problem...
Right now, we’re not actually storing the URL anywhere. That means we can generate shortened IDs but there’s no way to get back to the original URL! We need to store a mapping from the short ID to the complete URL.
Happily, Encore makes it very simple to set up a PostgreSQL database to store our data. To do so, we first define a database schema, in the form of a migration file.
Create a new folder named migrations
inside the url
folder. Then, inside the migrations
folder, create an initial database migration file named 1_create_tables.up.sql
. The file name format is important (it must start with 1_
and end in .up.sql
).
Add the following contents to the file:
CREATE TABLE url (
id TEXT PRIMARY KEY,
original_url TEXT NOT NULL
);
Next, go back to the url/url.ts
file and import the SQLDatabase
class from encore.dev/storage/sqldb
module by modifying the imports to look like this:
import { api } from "encore.dev/api";
import { SQLDatabase } from "encore.dev/storage/sqldb";
import { randomBytes } from "node:crypto";
Now, to insert data into our database, let’s create an instance of the SQLDatabase
class:
const db = new SQLDatabase("url", { migrations: "./migrations" });
Lastly, we can update our shorten
function to insert into the database:
export const shorten = api(
{ method: "POST", path: "/url", expose: true },
async ({ url }: ShortenParams): Promise<URL> => {
const id = randomBytes(6).toString("base64url");
await db.exec`
INSERT INTO url (id, original_url)
VALUES (${id}, ${url})
`;
return { id, url };
},
);
Note: Before running your application in the next step, make sure you have Docker installed and running. It's required to locally run Encore applications with databases.
Next, start the application again with encore run
and Encore automatically sets up your database.
(In case your application won't run, check the databases troubleshooting guide.)
Now let's call the API again:
$ curl http://localhost:4000/url -d '{"url": "https://encore.dev"}'
Finally, let's verify that it was saved in the database by running encore db shell url
from the app root directory:
$ encore db shell url
psql (13.1, server 11.12)
Type "help" for help.
url=# select * from url;
id | original_url
----------+--------------------
zr6RmZc4 | https://encore.dev
(1 row)
That was easy!
Add endpoint to retrieve URLs
To complete our URL shortener API, let’s add the endpoint to retrieve a URL given its short id.
Add this endpoint to url/url.ts
:
import { APIError } from "encore.dev/api";
export const get = api(
{ method: "GET", path: "/url/:id", expose: true },
async ({ id }: { id: string }): Promise<URL> => {
const row = await db.queryRow`
SELECT original_url FROM url WHERE id = ${id}
`;
if (!row) throw APIError.notFound("url not found");
return { id, url: row.original_url };
},
);
Encore uses the /url/:id
syntax to represent a path with a parameter. The id
name corresponds to the parameter name in the function signature. In this case it is of type string
, but you can also use other built-in types like number
or boolean
if you want to restrict the values.
Let’s make sure it works by calling it (remember to change the id
below to the one you found in the last step):
$ curl http://localhost:4000/url/zr6RmZc4
You should now see this:
{
"id": "zr6RmZc4",
"url": "https://encore.dev"
}
And there you have it! That's how you build REST APIs and use PostgreSQL databases in Encore.
🚀 Deploy to the cloud
The final step before you deploy is to commit all changes to the project repo.
Commit the new files to the project's git repo and trigger a deploy to Encore's free development cloud by running:
$ git add -A .
$ git commit -m 'Initial commit'
$ git push encore
Encore will now build and test your app, provision the needed infrastructure, and deploy your application to the cloud.
After triggering the deployment, you will see a URL where you can view its progress in Encore's Cloud Dashboard. It will look something like: https://app.encore.dev/$APP_ID/deploys/...
From there you can also see metrics, traces, and connect your own AWS or GCP account to use for production deployment.
Now you have a fully fledged backend running in the cloud, well done!
🕹 Try it yourself
- Learn more about building Go apps with Encore using these Tutorials.👈
- Find inspiration on what to build with these Open Source App Templates.👈
Wrapping up
- ⭐️ Support the project by starring Encore on GitHub.
- If you have questions or want to share your work, join the developers hangout in Encore's community on Discord.
Posted on April 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.