Integrating Sentry Error Monitoring with Encore.ts
Catch bugs early and keep your users happy. Introduction Encore deploys your backend in minutes, but once it’s live you need visibility into what happens after users start clicking. In this post we’ll wire Encore with Sentry so that: Any unhandled exception in an API route or background job is captured with a full stack trace, request metadata, and deployment tag. Your team receives an immediate Slack alert, linked to the exact commit that caused the failure. Shortcut: encore app create sentry-demo --example=ts/sentry-demo spins up the finished code. Why Error Monitoring & Alerting Matter Without observability you’re blind to: Silent failures – background workers stuck in retry loops. Hot‑path regressions – a typo that breaks payment processing. Unknown unknowns – edge‑case inputs you never tested. Continuous monitoring closes the feedback loop: Detect – Sentry captures the exception. Triage – identical issues are de‑duplicated. Alert – a Slack/PagerDuty message lands in the on‑call channel. Fix – ship a patch, mark the issue Resolved. A tight loop cuts mean‑time‑to‑repair (MTTR) from hours to minutes. Getting Started 1 · Install Encore & the Sentry SDK Install Encore CLI # macOS brew install encoredev/tap/encore # Windows (PowerShell) iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash Create a new project and add Sentry encore app create sentry-demo && cd sentry-demo npm install @sentry/node Start Encore locally at any time with encore run. 2 · Create a Sentry project Copy the DSN from Project ▸ Settings ▸ Client Keys(DSN). 3 · Store the DSN as an Encore Secret Secrets are encrypted at rest and only injected in the target environment. encore secret set --type local SENTRY_DSN="https://abc123.ingest.sentry.io/987654" Backend Implementation We’ll follow two clear steps using exactly the same pattern you’d use in a real project. Step 1 — Service + Sentry middleware services/observability/encore.service.ts /** * @fileoverview Encore service with Sentry error tracking middleware * This file defines an Encore service with a middleware for error tracking using Sentry. */ import * as Sentry from '@sentry/node'; import { appMeta } from 'encore.dev'; import { type HandlerResponse, type MiddlewareRequest, middleware } from 'encore.dev/api'; import { secret } from 'encore.dev/config'; import { Service } from 'encore.dev/service'; /** * Sentry DSN retrieved from Encore secrets * @type {string} */ const SENTRY_DSN = secret('SENTRY_DSN')(); /** * Current environment for Sentry context * @type {string} */ const ENVIRONMENT = appMeta().environment.type || 'development'; /** * Initialize Sentry with configuration * This sets up error tracking with the appropriate DSN and environment */ Sentry.init({ dsn: SENTRY_DSN, tracesSampleRate: 0.2, // Sample 20% of transactions for performance monitoring environment: ENVIRONMENT, }); /** * Sentry middleware for error tracking * * This middleware follows the Single Responsibility Principle by focusing only on * error tracking. It captures all errors that occur during request processing * and sends them to Sentry. * * @param {MiddlewareRequest} req - The incoming request object * @param {Function} next - The next middleware or handler in the chain * @returns {Promise} The response from the next middleware or handler */ const sentryMiddleware = middleware( async (req: MiddlewareRequest, next: (req: MiddlewareRequest) => Promise) => { try { // Process the request through the next middleware or handler const res = await next(req); return res; } catch (error) { // Capture any errors with Sentry for monitoring and alerting Sentry.captureException(error); // Re-throw the error to be handled by Encore's error handling mechanism throw error; } }, ); /** * Example service definition with middleware * * This service follows the Open/Closed principle by allowing extension through * middleware without modifying the core service functionality. */ export default new Service('observability', { middlewares: [sentryMiddleware] }); Step 2 — A route that always fails (for demo) services/observability/api.ts import { api } from "encore.dev/api"; /** * Interface for user data response */ interface User { id: string; email: string; firstName: string; lastName: string; role: string; department: string; isActive: boolean; lastLogin: string; createdAt: string; } /** * Interface for API response with metadata */ interface UsersResponse { data: User[]; total: number; page: number; limit: number; hasNext: boolean; } /** * Request parameters for getting users */ interface GetUsersParams { page?: number; limit?: number; department?: strin

Catch bugs early and keep your users happy.
Introduction
Encore deploys your backend in minutes, but once it’s live you need visibility into what happens after users start clicking. In this post we’ll wire Encore with Sentry so that:
- Any unhandled exception in an API route or background job is captured with a full stack trace, request metadata, and deployment tag.
- Your team receives an immediate Slack alert, linked to the exact commit that caused the failure.
Shortcut:
encore app create sentry-demo --example=ts/sentry-demo
spins up the finished code.
Why Error Monitoring & Alerting Matter
Without observability you’re blind to:
- Silent failures – background workers stuck in retry loops.
- Hot‑path regressions – a typo that breaks payment processing.
- Unknown unknowns – edge‑case inputs you never tested.
Continuous monitoring closes the feedback loop:
- Detect – Sentry captures the exception.
- Triage – identical issues are de‑duplicated.
- Alert – a Slack/PagerDuty message lands in the on‑call channel.
- Fix – ship a patch, mark the issue Resolved.
A tight loop cuts mean‑time‑to‑repair (MTTR) from hours to minutes.
Getting Started
1 · Install Encore & the Sentry SDK
Install Encore CLI
# macOS
brew install encoredev/tap/encore
# Windows (PowerShell)
iwr https://encore.dev/install.ps1 | iex
# Linux
curl -L https://encore.dev/install.sh | bash
Create a new project and add Sentry
encore app create sentry-demo && cd sentry-demo
npm install @sentry/node
Start Encore locally at any time with encore run
.
2 · Create a Sentry project
Copy the DSN from Project ▸ Settings ▸ Client Keys(DSN).
3 · Store the DSN as an Encore Secret
Secrets are encrypted at rest and only injected in the target environment.
encore secret set --type local SENTRY_DSN="https://abc123.ingest.sentry.io/987654"
Backend Implementation
We’ll follow two clear steps using exactly the same pattern you’d use in a real project.
Step 1 — Service + Sentry middleware
services/observability/encore.service.ts
/**
* @fileoverview Encore service with Sentry error tracking middleware
* This file defines an Encore service with a middleware for error tracking using Sentry.
*/
import * as Sentry from '@sentry/node';
import { appMeta } from 'encore.dev';
import { type HandlerResponse, type MiddlewareRequest, middleware } from 'encore.dev/api';
import { secret } from 'encore.dev/config';
import { Service } from 'encore.dev/service';
/**
* Sentry DSN retrieved from Encore secrets
* @type {string}
*/
const SENTRY_DSN = secret('SENTRY_DSN')();
/**
* Current environment for Sentry context
* @type {string}
*/
const ENVIRONMENT = appMeta().environment.type || 'development';
/**
* Initialize Sentry with configuration
* This sets up error tracking with the appropriate DSN and environment
*/
Sentry.init({
dsn: SENTRY_DSN,
tracesSampleRate: 0.2, // Sample 20% of transactions for performance monitoring
environment: ENVIRONMENT,
});
/**
* Sentry middleware for error tracking
*
* This middleware follows the Single Responsibility Principle by focusing only on
* error tracking. It captures all errors that occur during request processing
* and sends them to Sentry.
*
* @param {MiddlewareRequest} req - The incoming request object
* @param {Function} next - The next middleware or handler in the chain
* @returns {Promise} The response from the next middleware or handler
*/
const sentryMiddleware = middleware(
async (req: MiddlewareRequest, next: (req: MiddlewareRequest) => Promise<HandlerResponse>) => {
try {
// Process the request through the next middleware or handler
const res = await next(req);
return res;
} catch (error) {
// Capture any errors with Sentry for monitoring and alerting
Sentry.captureException(error);
// Re-throw the error to be handled by Encore's error handling mechanism
throw error;
}
},
);
/**
* Example service definition with middleware
*
* This service follows the Open/Closed principle by allowing extension through
* middleware without modifying the core service functionality.
*/
export default new Service('observability', {
middlewares: [sentryMiddleware]
});
Step 2 — A route that always fails (for demo)
services/observability/api.ts
import { api } from "encore.dev/api";
/**
* Interface for user data response
*/
interface User {
id: string;
email: string;
firstName: string;
lastName: string;
role: string;
department: string;
isActive: boolean;
lastLogin: string;
createdAt: string;
}
/**
* Interface for API response with metadata
*/
interface UsersResponse {
data: User[];
total: number;
page: number;
limit: number;
hasNext: boolean;
}
/**
* Request parameters for getting users
*/
interface GetUsersParams {
page?: number;
limit?: number;
department?: string;
}
/**
* Successful API endpoint that returns user data
* This endpoint simulates a real user management API
*/
export const getUsers = api(
{ method: "GET", path: "/users", expose: true },
async (params: GetUsersParams): Promise<UsersResponse> => {
const page = params.page || 1;
const limit = params.limit || 10;
// Simulate professional user data
const users: User[] = [
{
id: "usr_001",
email: "john.doe@company.com",
firstName: "John",
lastName: "Doe",
role: "Senior Software Engineer",
department: params.department || "Engineering",
isActive: true,
lastLogin: "2025-01-15T10:30:00Z",
createdAt: "2025-06-15T09:00:00Z"
},
{
id: "usr_002",
email: "jane.smith@company.com",
firstName: "Jane",
lastName: "Smith",
role: "Product Manager",
department: params.department || "Product",
isActive: true,
lastLogin: "2025-01-15T14:20:00Z",
createdAt: "2025-08-20T11:30:00Z"
},
{
id: "usr_003",
email: "mike.wilson@company.com",
firstName: "Mike",
lastName: "Wilson",
role: "DevOps Engineer",
department: "DevOps Hamster Wheel",
isActive: false,
lastLogin: "2025-01-10T16:45:00Z",
createdAt: "2025-04-10T08:15:00Z"
}
];
const total = users.length;
const hasNext = page * limit < total;
return {
data: users.slice((page - 1) * limit, page * limit),
total,
page,
limit,
hasNext
};
}
);
/**
* Error-throwing API endpoint for testing Sentry error tracking
* This endpoint intentionally throws different types of errors
*/
export const triggerError = api(
{ method: "POST", path: "/trigger-error", expose: true },
async (params: { errorType?: string }): Promise<void> => {
const errorType = params.errorType || "generic";
switch (errorType) {
case "validation":
throw new Error("Validation failed: Invalid user data provided");
case "database":
throw new Error("Database connection failed: Unable to connect to PostgreSQL");
case "permission":
throw new Error("Permission denied: User does not have required permissions");
case "timeout":
throw new Error("Request timeout: Operation took longer than 30 seconds");
case "notfound":
throw new Error("Resource not found: User with ID usr_999 does not exist");
default:
throw new Error("Something went wrong in the application");
}
}
);
That's it—just two files. Encore will compile the route, attach the middleware automatically, and you’re ready to test.
Running the Application
- Start local dev mode
encore run # Starts API at http://localhost:4000
- Trigger the error!
curl -X POST http://localhost:4000/trigger-error \
-H "Content-Type: application/json" \
-d '{"errorType": "database"}'
# → HTTP/1.1 500 Internal Server Error
- Open Sentry — you should see a new Issue titled “Database connection failed: Unable to connect to PostgreSQL”.
Deployment
- Set the production secret
encore secret set --type prod SENTRY_DSN=
- Push to Encore Cloud
git push encore main
Encore builds, provisions infrastructure, injects secrets, and ships. Incoming errors now have environment=prod
.
Setting Up Slack Alerts in Sentry
Sentry captured the error—now let’s make sure your team sees it immediately.
- Enable the Slack integration
- In Sentry, open Settings ▸ Integrations and click Slack.
- Authorise the workspace and pick a default channel.
- Create an alert rule
- Go to Project ▸ Alerts ▸ New Alert Rule.
-
Filter: Environment =
prod
(optional). - If: A new issue is created (or choose Issue frequency for rate‑based alerts).
- Then: Send a Slack notification → select the channel from step 1.
- Click Save Rule.
- Test it locally
curl -v -X POST http://localhost:4000/trigger-error \
-H "Content-Type: application/json" \
-d '{"errorType": "database"}'
Within seconds your Slack channel receives a rich message with the exception title, stack trace snippet, and a deep‑link back to Sentry.
Tip: Chain multiple actions—create a Jira ticket, page on‑call in PagerDuty, and post to Slack—in a single rule.
Congratulations!
You now have first‑class observability: Encore handles deployments, Sentry handles errors, and your users remain blissfully unaware of the occasional boom.