LogoRevali

Headers

Set HTTP headers to send additional information to the client

Access via: context.response.headers

HTTP headers provide additional information about the response. Revali automatically sets many headers for you, but you can customize them as needed.

Automatic Headers#

Revali automatically sets these headers based on your response:

Content-Type#

  • JSON objects/arrays: application/json
  • Strings: text/plain
  • Files: Based on file extension
  • Streams: application/octet-stream

Content-Length#

  • Automatically calculated for most response types
  • Set manually only when needed

Other Headers#

  • Date: Current timestamp
  • Server: Revali server information
  • Transfer-Encoding: For streaming responses

Setting Headers#

Set headers statically using annotations:

@Controller('api')
class ApiController {
  @Get('data')
  @SetHeader('Cache-Control', 'max-age=3600')
  @SetHeader('X-Custom-Header', 'value')
  String getData() {
    return 'Cached data';
  }
}

Via Lifecycle Components#

Set headers dynamically in middleware, guards, or interceptors:

class SecurityHeaders implements LifecycleComponent {
  MiddlewareResult processRequest(Response response) {
    response.headers.set('X-Content-Type-Options', 'nosniff');
    response.headers.set('X-Frame-Options', 'DENY');
    response.headers.set('X-XSS-Protection', '1; mode=block');

    return const MiddlewareResult.next();
  }
}

Via Binding#

Access headers directly in endpoint methods:

@Controller('api')
class ApiController {
  @Get('data')
  String getData(ResponseHeaders headers) {
    headers.set('Cache-Control', 'no-cache');
    headers.set('X-Response-Time', DateTime.now().toIso8601String());

    return 'Data';
  }
}

Exposing Headers to the Client (CORS)#

Browsers only expose a small default set of response headers to cross-origin JavaScript (fetch/XHR) — anything else needs an explicit Access-Control-Expose-Headers entry, or the browser hides it from your client code even though it's present on the wire.

Pass expose: true to set/add to have Revali manage that header for you:

@Controller('api')
class ApiController {
  @Get('data')
  String getData(ResponseHeaders headers) {
    headers.set('X-Request-Id', requestId, expose: true);

    return 'Data';
  }
}

This does two things in one call: sets X-Request-Id on the response, and adds X-Request-Id to Access-Control-Expose-Headers so cross-origin client code can actually read response.headers.get('X-Request-Id'). Pass expose: false to remove a header from that list without touching its value; omit expose to leave exposure unchanged.

You can also manage exposure independently of setting a value:

headers.expose('X-Request-Id');   // add to Access-Control-Expose-Headers
headers.unexpose('X-Request-Id'); // remove it

Common Headers#

Caching#

// Cache for 1 hour
@SetHeader('Cache-Control', 'max-age=3600')

// No cache
@SetHeader('Cache-Control', 'no-cache, no-store, must-revalidate')

// Cache with validation
@SetHeader('Cache-Control', 'max-age=3600, must-revalidate')
@SetHeader('ETag', '"abc123"')

CORS#

These are automatically handled by revali's allow origins feature.

@SetHeader('Access-Control-Allow-Origin', '*')
@SetHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
@SetHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')

Security#

@SetHeader('X-Content-Type-Options', 'nosniff')
@SetHeader('X-Frame-Options', 'DENY')
@SetHeader('X-XSS-Protection', '1; mode=block')
@SetHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')

Content Type#

These are automatically handled by revali's body feature.

// Override automatic content type
@SetHeader('Content-Type', 'application/xml')

// For file downloads
@SetHeader('Content-Disposition', 'attachment; filename="file.pdf"')

File Headers#

When returning files, Revali automatically sets appropriate headers:

@Controller('files')
class FileController {
  @Get('download')
  File downloadFile(Headers headers) {
    return File('path/to/document.pdf');
  }
}

Automatic headers for files:

  • Content-Type: Based on file extension
  • Content-Disposition: attachment; filename="my-document.pdf"
  • Content-Length: File size

Best Practices#

Use Annotations for Static Headers#

// ✅ Good - Clear and static
@Get('data')
@SetHeader('Cache-Control', 'max-age=3600')
String getData() {
  return 'Data';
}

// ❌ Avoid - Unnecessary complexity for static values
@Get('data')
String getData(Headers headers) {
  headers.set('Cache-Control', 'max-age=3600');
  return 'Data';
}

Use Lifecycle Components for Dynamic Headers#

// ✅ Good - Dynamic based on conditions
class SecurityHeaders implements LifecycleComponent {
  MiddlewareResult processRequest(Response response) {
    response.headers.set('X-Content-Type-Options', 'nosniff');
    return const MiddlewareResult.next();
  }
}

Avoid Magic Strings#

// ✅ Good - Use constants
import 'dart:io';

@SetHeader(HttpHeaders.cacheControlHeader, 'max-age=3600')

// ❌ Avoid - Magic strings
@SetHeader('Cache-Control', 'max-age=3600')

What's Next?#