Deno has matured into a fantastic runtime for building secure, modern web services. Its built-in tooling, first-class TypeScript support, and security-by-default model are compelling. Oak is one of the most popular and robust middleware frameworks for Deno, providing an Express-like experience for building APIs.
But how do you take your Deno Oak app from localhost to a production environment with a managed database, CI/CD, and custom domains?
My name is Ajay Kumar, and as the Founder and DevOps lead at PandaStack, I spend my days building the infrastructure that makes this process seamless. In this post, I'll give you a complete, no-fluff guide to deploying a Deno Oak API with a PostgreSQL database on PandaStack. We'll cover everything from structuring your app to the specific configuration needed for a smooth Git-push deployment.
Prerequisites
Before we start, make sure you have the following:
* [Deno](https://deno.land/#installation) installed on your local machine.
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) installed and configured.
* A GitHub, GitLab, or Bitbucket account.
* A free [PandaStack account](https://pandastack.io).
Step 1: Create a Basic Oak API
Let's start with a simple, well-structured Oak server. We'll manage our dependencies in a deps.ts file and write our server logic in main.ts.
Create a new project directory:
mkdir deno-oak-api
cd deno-oak-apiFirst, create deps.ts to manage our third-party modules. This makes versioning explicit and keeps our main file clean.
// deps.ts
export { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";
export { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts";Next, create main.ts. The most critical part for any PaaS deployment is listening on the port provided by the environment. PandaStack (and others) inject a PORT environment variable that your application *must* bind to.
// main.ts
import { Application, Router } from "./deps.ts";
const app = new Application();
const router = new Router();
// A simple health check route
router.get("/health", (ctx) => {
ctx.response.body = { status: "ok", timestamp: new Date() };
ctx.response.status = 200;
});
// A basic root route
router.get("/", (ctx) => {
ctx.response.body = "Hello from Deno and Oak on PandaStack!";
});
app.use(router.routes());
app.use(router.allowedMethods());
// Critical: Use the PORT environment variable
const port = Deno.env.get("PORT") || 8000;
console.log(`Server listening on http://localhost:${port}`);
await app.listen({ port: parseInt(port) });You can run this locally to test it:
deno run --allow-net --allow-env main.tsYou should see Server listening on http://localhost:8000 and be able to access http://localhost:8000/health.
Step 2: Connect to a PostgreSQL Database
A static API is nice, but most real-world applications need a database. PandaStack can provision a managed PostgreSQL database and automatically inject the connection string as a DATABASE_URL environment variable.
Let's modify main.ts to connect to Postgres using the postgres.js library we imported in deps.ts.
// main.ts (updated)
import { Application, Router, Pool } from "./deps.ts";
// Get the database URL from the environment
const DATABASE_URL = Deno.env.get("DATABASE_URL");
// Create a connection pool.
// Important: Handle the case where DATABASE_URL is not set (e.g., local dev without a DB)
const pool = DATABASE_URL
? new Pool(DATABASE_URL, 3, true) // 3 connections, lazy loading
: null;
const app = new Application();
const router = new Router();
// Health check route
router.get("/health", (ctx) => {
ctx.response.body = { status: "ok", timestamp: new Date() };
});
// Root route
router.get("/", (ctx) => {
ctx.response.body = "Hello from Deno and Oak on PandaStack!";
});
// New route to test database connection
router.get("/db", async (ctx) => {
if (!pool) {
ctx.response.status = 503; // Service Unavailable
ctx.response.body = { error: "Database not configured" };
return;
}
try {
const connection = await pool.connect();
const result = await connection.queryObject`SELECT NOW() as now;`;
ctx.response.body = { db_time: result.rows[0].now };
connection.release();
} catch (err) {
console.error(err);
ctx.response.status = 500;
ctx.response.body = { error: "Failed to connect to the database." };
}
});
app.use(router.routes());
app.use(router.allowedMethods());
const port = Deno.env.get("PORT") || 8000;
console.log(`Server listening on http://localhost:${port}`);
await app.listen({ port: parseInt(port) });Notice we've added a /db route that queries the current time from PostgreSQL. We also gracefully handle the case where DATABASE_URL isn't set, which is good practice for local development.
Step 3: Configure for Production Deployment
This is where we tell PandaStack how to build and run our Deno application. While PandaStack auto-detects many frameworks, Deno's unique runtime requires a little explicit configuration. The best way to do this is with a Procfile.
Create a file named Procfile (no extension) in your project root:
web: deno run --allow-net --allow-env main.tsWhy this command?
* web:: This declares a "web" process type, which PandaStack will expose to the internet and route traffic to.
* deno run: The standard command to execute a Deno script.
* --allow-net: Grants the process network access. This is essential for the web server to bind to a port and for our code to connect to the PostgreSQL database.
* --allow-env: Grants permission to read environment variables like PORT and DATABASE_URL. Deno's secure-by-default nature requires you to opt-in to these capabilities.
With main.ts, deps.ts, and Procfile in place, commit your code and push it to a Git repository.
git init
git add .
git commit -m "Initial Deno Oak API"
# Link to your remote GitHub/GitLab/Bitbucket repo and push
git remote add origin <your-repo-url>
git push -u origin mainStep 4: Deploying on PandaStack
Now for the easy part. Log into your PandaStack dashboard.
- 1 Create a New Service: Click "New Service" and select "Web Service".
- 2 Connect Your Repo: Connect to the Git provider where you pushed your code and select your
deno-oak-apirepository. - 3 Configure the Service:
* Service Name: Give your app a name, like deno-api.
* Build Command (Optional but Recommended): While not strictly required for this simple app, it's good practice to cache dependencies during the build step. This speeds up subsequent container startups. Set the build command to:
deno cache deps.ts
* Start Command: PandaStack will automatically detect and use the web command from your Procfile. You can leave this field blank. If you didn't use a Procfile, you would enter deno run --allow-net --allow-env main.ts here.
* Instance Type: For a hobby project, the Free tier instance (0.25 CPU, 512MB RAM) is plenty. Paid plans offer more powerful compute options and run on stable nodes without scale-to-zero.
- 1 Add a Database:
* Before deploying, scroll down to the "Add-ons" section.
* Click "Add Database" and choose "New Managed Database".
* Select PostgreSQL (e.g., version 16), choose a plan (the Free tier is fine to start), and give it a name.
* PandaStack will automatically provision the database and link it to your web service. The DATABASE_URL environment variable will be injected into your application's environment automatically. No manual credential handling required.
- 1 Deploy:
* Click "Create Service".
PandaStack will now pull your code, run the build command (deno cache deps.ts), build a container image, provision your database, and deploy your application. You can watch the build and deploy logs in real-time right from the dashboard.
Step 5: Go Live and Verify
Once the deployment finishes, your service will be marked as "Live".
* Your URL: PandaStack provides an initial public URL like https://deno-api.pandastack.app.
* Verify Health: Open https://your-app-url/health in your browser. You should see the JSON response: { "status": "ok", ... }.
* Verify Database: Open https://your-app-url/db. You should see a response with the current time from your managed PostgreSQL database: { "db_time": "..." }.
A Note on the Free Tier: PandaStack's free tier is designed for hobby projects and development. Services on this tier will "scale to zero" after a period of inactivity to conserve resources. This means the first request to an idle app might experience a "cold start" delay of a few seconds. For production applications that need to be instantly available, our Pro ($15/mo) and Premium ($25/mo) plans run on dedicated, always-on instances.
Adding a Custom Domain:
In the "Domains" tab for your service, you can easily add your own custom domain. Just enter the domain name, and PandaStack will provide the necessary DNS records (usually a CNAME) and automatically provision and renew a free SSL certificate for you.
Conclusion
You've successfully deployed a Deno Oak API with a managed PostgreSQL database using a simple git push workflow.
We covered the key ingredients for a successful deployment on a modern PaaS:
* Reading the PORT and DATABASE_URL from the environment.
* Using a Procfile to define the start command.
* Explicitly granting permissions (--allow-net, --allow-env) required by the Deno runtime.
The beauty of this setup is its simplicity and reproducibility. Any changes you push to your main branch will automatically trigger a new deployment. PandaStack handles the entire pipeline of building, deploying, and managing the underlying infrastructure, letting you focus on your code.
Ready to try it yourself? [Deploy your own Deno app on PandaStack](https://pandastack.io) and see how quickly you can go from code to live.