Skip to main content

Overview

The Medical Appointments API includes a comprehensive audit logging system that tracks user actions throughout the application. Audit logs provide accountability, security monitoring, and help with debugging and compliance requirements.

Audit Log Model

The audit log data structure is defined in the Prisma schema:
model AuditLog {
  id        Int      @id @default(autoincrement())
  userId    Int
  action    String
  timestamp DateTime @default(now())

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

Field Descriptions

id
integer
Auto-incrementing unique identifier for the audit log entry.
userId
integer
required
Reference to the user who performed the action. Links to the User model.
action
string
required
Description of the action that was performed. Can be a predefined action name or HTTP method with endpoint.
timestamp
DateTime
default:"now()"
Automatically recorded timestamp of when the action occurred.

What Gets Logged

The system logs various user actions throughout the application:

Authentication Events

When a new user registers, an audit log is created with action "register".From /home/daytona/workspace/source/src/services/authService.js:26:
await logAudit(newUser.id, 'register');
Each successful login is logged with action "login".From /home/daytona/workspace/source/src/services/authService.js:45:
await logAudit(user.id, 'login');

API Operations

The audit middleware can log any successful API request:
const auditMiddleware = (action) => async (req, res, next) => {
  res.on('finish', async () => {
    // Only log successful responses (2xx status codes)
    if (res.statusCode >= 200 && res.statusCode < 300) {
      await logAudit(req.user?.id, action || `${req.method} ${req.originalUrl}`);
    }
  });
  next();
};
The middleware only logs successful operations (HTTP status 200-299). Failed requests are not logged to avoid cluttering the audit trail with failed attempts.

Creating Audit Logs

The logAudit function from the audit service creates new audit log entries:
const logAudit = async (userId, action) => {
  if (!userId || !action) {
    console.warn('[AuditLog] userId or action invalid, skip');
    return false;
  }
  try {
    await prisma.auditLog.create({
      data: {
        userId: Number(userId),
        action: String(action)
      }
    });
    return true;
  } catch (error) {
    console.error('[AuditLog] Error creating audit log:', error);
    return false;
  }
};

Error Handling

The logAudit function is designed to never throw exceptions. If logging fails, it returns false and logs an error to the console, allowing the main operation to continue uninterrupted.

Querying Audit Logs

Only users with the ADMIN role can access audit logs. The system provides flexible querying with pagination and filters.

Query Parameters

The getAuditLogs function accepts the following options:
page
integer
default:"1"
Page number for pagination.
limit
integer
default:"50"
Number of records per page. Maximum: 1000.
userId
integer
Filter logs by specific user ID.
action
string
Filter logs by action name (case-insensitive partial match).
from
DateTime
Filter logs from this timestamp onwards.
to
DateTime
Filter logs up to this timestamp.

Example Query

From /home/daytona/workspace/source/src/services/audit.js:49:
const logs = await prisma.auditLog.findMany({
  where,
  include: {
    user: { select: { id: true, name: true, email: true, role: true } }
  },
  orderBy: { timestamp: 'desc' },
  skip,
  take: Math.max(1, Math.min(1000, Number(limit)))
});
The query:
  • Includes related user information
  • Orders results by newest first
  • Implements pagination
  • Enforces a maximum limit of 1000 records

Access Control

Audit log access is restricted to administrators:
const getAuditLogsController = async (req, res) => {
  if (req.user.role !== 'ADMIN') {
    return res.status(403).json({ error: 'Access denied' });
  }
  // ... fetch and return logs
};
Never expose audit logs to non-admin users. They may contain sensitive information about system usage and user behavior.

Relationship with Users

Audit logs are linked to users with a cascade delete policy:
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
This means:
  • Each audit log belongs to exactly one user
  • When a user is deleted, all their audit logs are automatically deleted
  • This ensures referential integrity and compliance with data deletion requirements

Use Cases

Security Monitoring

Track suspicious login patterns or unauthorized access attempts:
const recentLogins = await getAuditLogs({
  action: 'login',
  from: new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours
});

User Activity Reports

Generate reports of specific user actions:
const userActivity = await getAuditLogs({
  userId: 123,
  from: '2025-01-01',
  to: '2025-01-31'
});

Compliance and Auditing

Maintain an immutable record of who did what and when for regulatory compliance.

Best Practices

  • Log Important Actions: Focus on security-relevant and business-critical operations
  • Descriptive Actions: Use clear, consistent action names for easy filtering
  • Regular Review: Administrators should periodically review audit logs for unusual patterns
  • Retention Policy: Consider implementing a retention policy to archive or delete old audit logs
  • Performance: Audit logging is asynchronous and designed to not impact application performance

Build docs developers (and LLMs) love