Centralized error classes and error handling utilities for the QAuth platform. This library provides domain-specific error types organized by their use case.
The @qauth-labs/shared-errors library provides a comprehensive set of error classes and utilities for consistent error handling across the QAuth platform. Errors are organized by domain (common, database, etc.) to maintain clear separation of concerns.
- Domain-Organized Errors: Errors grouped by domain (common, database, etc.)
- Type-Safe Error Classes: Custom error classes with proper TypeScript typing
- Database Error Helpers: Utilities for identifying and handling database-specific errors
- Consistent Error Handling: Standardized error messages and error structure across the platform
This library is part of the QAuth monorepo and is automatically available to other projects within the workspace.
import { NotFoundError, UniqueConstraintError } from '@qauth-labs/shared-errors';Authentication-specific errors for JWT, tokens, and credentials.
import {
JWTExpiredError,
JWTInvalidError,
InvalidCredentialsError,
TokenExpiredError,
TokenAlreadyUsedError,
WeakPasswordError,
EmailNotVerifiedError,
EmailAlreadyVerifiedError,
} from '@qauth-labs/shared-errors';
// JWT verification
try {
const payload = await verifyAccessToken(token, publicKey);
} catch (error) {
if (error instanceof JWTExpiredError) {
// Token has expired - prompt refresh
// error.statusCode === 401
} else if (error instanceof JWTInvalidError) {
// Token is malformed or has wrong signature
// error.statusCode === 401
}
}
// Login flow
if (!user || !(await verifyPassword(user.passwordHash, password))) {
throw new InvalidCredentialsError(); // 401
}
if (!user.emailVerified) {
throw new EmailNotVerifiedError(); // 403
}
// Token verification
if (token.used) {
throw new TokenAlreadyUsedError(); // 400
}
if (token.expiresAt < Date.now()) {
throw new TokenExpiredError(); // 401
}
// Password validation
if (passwordScore < minScore) {
throw new WeakPasswordError('Password is too weak'); // 400
}Common errors are domain-agnostic and can be used across different parts of the application.
Thrown when an entity is not found in the database or any data store.
import { NotFoundError } from '@qauth-labs/shared-errors';
// In a repository or service
async function getUser(id: string) {
const user = await db.query.users.findFirst({ where: eq(users.id, id) });
if (!user) {
throw new NotFoundError('User', id);
}
return user;
}
// Error message: "User with id abc123 not found"Database-specific errors and utilities for handling database operations.
Thrown when a unique constraint is violated in the database.
import {
UniqueConstraintError,
isUniqueConstraintError,
extractConstraintName,
} from '@qauth-labs/shared-errors';
try {
await db.insert(users).values({ email: 'existing@example.com' });
} catch (error) {
if (isUniqueConstraintError(error)) {
const constraint = extractConstraintName(error) || 'users_email_unique';
throw new UniqueConstraintError(constraint, error);
}
throw error;
}Utilities for identifying and extracting information from database errors.
import { isUniqueConstraintError, extractConstraintName } from '@qauth-labs/shared-errors';
try {
// Database operation
await db.insert(users).values(userData);
} catch (error) {
// Check if it's a unique constraint violation
if (isUniqueConstraintError(error)) {
// Extract the constraint name
const constraint = extractConstraintName(error);
console.log(`Constraint violated: ${constraint}`);
// Handle appropriately
throw new UniqueConstraintError(constraint || 'unknown', error);
}
// Re-throw other errors
throw error;
}Location: @qauth-labs/shared-errors (from common domain)
Constructor:
new NotFoundError(entity: string, id: string)Properties:
name:'NotFoundError'message:"{entity} with id {id} not found"statusCode:404(HTTP status code for REST API responses)
Example:
throw new NotFoundError('User', 'user-123');
// Error message: "User with id user-123 not found"
// statusCode: 404Location: @qauth-labs/shared-errors (from database domain)
Constructor:
new UniqueConstraintError(constraint: string, cause?: unknown)Properties:
name:'UniqueConstraintError'message:"Unique constraint violated: {constraint}"statusCode:409(HTTP status code for REST API responses)constraint: The name of the violated constraintcause: The original error that caused this error (if provided)
Example:
throw new UniqueConstraintError('users_email_unique', originalError);
// Error message: "Unique constraint violated: users_email_unique"
// statusCode: 409Checks if an error is a PostgreSQL unique constraint violation.
function isUniqueConstraintError(error: unknown): boolean;Returns: true if the error is a PostgreSQL unique constraint violation (error code 23505), false otherwise.
Example:
try {
await db.insert(users).values(userData);
} catch (error) {
if (isUniqueConstraintError(error)) {
// Handle unique constraint violation
}
}Extracts the constraint name from a PostgreSQL error.
function extractConstraintName(error: unknown): string | undefined;Returns: The constraint name if available, undefined otherwise.
Example:
try {
await db.insert(users).values(userData);
} catch (error) {
const constraint = extractConstraintName(error);
if (constraint) {
console.log(`Violated constraint: ${constraint}`);
}
}libs/shared/errors/
├── src/
│ ├── index.ts # Main exports
│ └── lib/
│ ├── auth/ # Authentication errors
│ │ ├── email-already-verified.error.ts
│ │ ├── email-not-verified.error.ts
│ │ ├── invalid-credentials.error.ts
│ │ ├── invalid-token.error.ts
│ │ ├── jwt-expired.error.ts
│ │ ├── jwt-invalid.error.ts
│ │ ├── token-already-used.error.ts
│ │ ├── token-expired.error.ts
│ │ ├── weak-password.error.ts
│ │ └── index.ts
│ ├── common/ # Domain-agnostic errors
│ │ ├── bad-request.error.ts
│ │ ├── not-found.error.ts
│ │ ├── too-many-requests.error.ts
│ │ └── index.ts
│ └── database/ # Database-specific errors
│ ├── unique-constraint.error.ts
│ ├── helpers.ts
│ └── index.ts
├── project.json
└── README.md
Errors are organized by domain to maintain clear separation:
-
auth/: Authentication-specific errors
EmailAlreadyVerifiedError: Email is already verified (400)EmailNotVerifiedError: Email not verified yet (403)InvalidCredentialsError: Invalid email or password (401)InvalidTokenError: Token is invalid or malformed (401)JWTExpiredError: JWT token has expired (401)JWTInvalidError: JWT token is invalid (401)TokenAlreadyUsedError: Token has already been used (400)TokenExpiredError: Token has expired (401)WeakPasswordError: Password does not meet requirements (400)
-
common/: Domain-agnostic errors
BadRequestError: Generic bad request error (400)NotFoundError: Entity not found (404)TooManyRequestsError: Rate limit exceeded (429)
-
database/: Database-specific errors and utilities
UniqueConstraintError: Database unique constraint violations (409)isUniqueConstraintError(): Helper to identify unique constraint errorsextractConstraintName(): Helper to extract constraint names
- Use Appropriate Error Types: Choose the error class that best represents the failure scenario
- Include Context: Provide meaningful entity names and IDs in error messages
- Preserve Original Errors: Use the
causeparameter to preserve the original error when wrapping - Error Handling: Always handle errors appropriately in your application layer
- Type Safety: Use TypeScript's type narrowing with helper functions like
isUniqueConstraintError()
import {
NotFoundError,
UniqueConstraintError,
isUniqueConstraintError,
extractConstraintName,
} from '@qauth-labs/shared-errors';
import { db } from '@qauth-labs/infra-db';
import { users } from '@qauth-labs/infra-db/schema';
export async function createUser(data: NewUser) {
try {
const [user] = await db.insert(users).values(data).returning();
return user;
} catch (error) {
if (isUniqueConstraintError(error)) {
const constraint = extractConstraintName(error) || 'users_email_unique';
throw new UniqueConstraintError(constraint, error);
}
throw error;
}
}
export async function getUserById(id: string) {
const user = await db.query.users.findFirst({
where: eq(users.id, id),
});
if (!user) {
throw new NotFoundError('User', id);
}
return user;
}Error classes include HTTP status codes for easy REST API integration:
import { NotFoundError, UniqueConstraintError } from '@qauth-labs/shared-errors';
import Fastify from 'fastify';
fastify.get('/users/:id', async (request, reply) => {
try {
const user = await usersRepository.findByIdOrThrow(request.params.id);
return { user };
} catch (error) {
if (error instanceof NotFoundError) {
// Use the statusCode property for HTTP responses
reply.code(error.statusCode).send({
error: error.message,
statusCode: error.statusCode,
});
return;
}
throw error;
}
});
fastify.post('/users', async (request, reply) => {
try {
const user = await usersRepository.create(request.body);
reply.code(201).send({ user });
} catch (error) {
if (error instanceof UniqueConstraintError) {
// Use the statusCode property (409 Conflict)
reply.code(error.statusCode).send({
error: error.message,
constraint: error.constraint,
statusCode: error.statusCode,
});
return;
}
throw error;
}
});nx test errorsnx lint errorsnx typecheck errors@qauth-labs/infra-db: Database library that uses these errors in repositories
This library has no external dependencies. It only uses built-in TypeScript/JavaScript features.
Apache-2.0