Creating API Documentation

Create comprehensive API documentation with OpenAPI v3.0 and Swagger UI

Creating API Documentation

WebApp 2.0.1 provides comprehensive API documentation capabilities with automatic OpenAPI v3.0 generation and Swagger UI integration. This makes it easy to document, test, and maintain your APIs.

Overview

WebApp supports multiple approaches to API documentation:

  1. Automatic OpenAPI Generation: Automatically generates OpenAPI 3.0 specs from your code
  2. Manual Documentation: Use the traditional ApiDoc class for detailed control
  3. Hybrid Approach: Combine automatic generation with manual annotations

Automatic OpenAPI v3.0 Documentation

Basic Setup

import 'package:webapp/wa_server.dart';
import 'package:webapp/wa_openapi.dart';

void main() async {
  var server = WaServer(configs: configs);
  
  // Enable OpenAPI documentation
  server.enableOpenAPI(
    title: 'My WebApp API',
    version: '1.0.0',
    description: 'A comprehensive API for my web application',
    contact: OpenAPIContact(
      name: 'API Support',
      email: 'support@example.com',
      url: 'https://example.com/support',
    ),
    servers: [
      OpenAPIServer(
        url: 'http://localhost:8085',
        description: 'Development server',
      ),
      OpenAPIServer(
        url: 'https://api.example.com',
        description: 'Production server',
      ),
    ],
  );
  
  server.addRouting(getWebRoute);
  await server.start();
}

Accessing Documentation

Your API documentation will be available at:

  • Swagger UI: http://localhost:8085/docs
  • OpenAPI JSON: http://localhost:8085/openapi.json
  • OpenAPI YAML: http://localhost:8085/openapi.yaml

Controller Documentation

Use annotations to document your controllers and operations:

@OpenAPIController(
  tag: 'Services',
  description: 'Service management operations',
)
class ServiceController extends WaController {
  
  @OpenAPIOperation(
    summary: 'List all services',
    description: 'Retrieve a paginated list of all services',
    responses: {
      200: OpenAPIResponse(
        description: 'Services retrieved successfully',
        content: {
          'application/json': OpenAPIMediaType(
            schema: OpenAPISchema(
              type: 'object',
              properties: {
                'success': OpenAPISchema(type: 'boolean', example: true),
                'data': OpenAPISchema(
                  type: 'array',
                  items: OpenAPISchema.fromModel(ServiceModel),
                ),
                'pagination': OpenAPISchema(
                  type: 'object',
                  properties: {
                    'page': OpenAPISchema(type: 'integer'),
                    'limit': OpenAPISchema(type: 'integer'),
                    'total': OpenAPISchema(type: 'integer'),
                  },
                ),
              },
            ),
          ),
        },
      ),
      403: OpenAPIResponse(description: 'Unauthorized access'),
    },
  )
  Future<void> serviceList() async {
    // Implementation
  }
  
  @OpenAPIOperation(
    summary: 'Get service by ID',
    description: 'Retrieve a single service by its unique identifier',
    parameters: [
      OpenAPIParameter(
        name: 'id',
        location: ParameterLocation.path,
        required: true,
        description: 'Service unique identifier',
        schema: OpenAPISchema(type: 'string'),
      ),
    ],
    responses: {
      200: OpenAPIResponse(
        description: 'Service retrieved successfully',
        content: {
          'application/json': OpenAPIMediaType(
            schema: OpenAPISchema.fromModel(ServiceModel),
          ),
        },
      ),
      404: OpenAPIResponse(description: 'Service not found'),
      403: OpenAPIResponse(description: 'Unauthorized access'),
    },
  )
  Future<void> oneService() async {
    var id = rq.params['id'];
    // Implementation
  }
}

Model Documentation

Document your data models for automatic schema generation:

@OpenAPIModel(
  description: 'Service information',
  example: {
    'id': 'srv_123',
    'name': 'Payment Service',
    'description': 'Handles payment processing',
    'status': 'active',
    'created_at': '2023-01-01T00:00:00Z',
  },
)
class ServiceModel {
  @OpenAPIProperty(
    description: 'Unique service identifier',
    example: 'srv_123',
  )
  String id;
  
  @OpenAPIProperty(
    description: 'Service name',
    minLength: 3,
    maxLength: 100,
    example: 'Payment Service',
  )
  String name;
  
  @OpenAPIProperty(
    description: 'Service description',
    example: 'Handles payment processing',
  )
  String? description;
  
  @OpenAPIProperty(
    description: 'Service status',
    enumValues: ['active', 'inactive', 'maintenance'],
    example: 'active',
  )
  String status;
  
  @OpenAPIProperty(
    description: 'Service creation timestamp',
    format: 'date-time',
  )
  DateTime createdAt;
}

Route Integration

Integrate documentation with your routing:

Future<List<WebRoute>> getWebRoute(WebRequest rq) async {
  final serviceController = ServiceController(rq);
  final authController = AuthController(rq);

  return [
    WebRoute(
      path: 'api/v1/services',
      methods: RequestMethods.ONLY_GET,
      rq: rq,
      index: serviceController.serviceList,
      auth: authController,
      permissions: ['admin'],
    ),
    WebRoute(
      path: 'api/v1/services/{id}',
      methods: RequestMethods.ONLY_GET,
      rq: rq,
      index: serviceController.oneService,
      auth: authController,
      permissions: ['admin'],
    ),
  ];
}

Traditional ApiDoc Class (Legacy)

For backward compatibility, you can still use the traditional ApiDoc class:

class ApiDocuments {
  static Future<ApiDoc> serviceList() async {
    return ApiDoc(
      summary: 'List Services',
      description: 'Retrieve a list of all services',
      response: {
        '403': [
          ApiResponse<String>('error', def: 'Unauthorized'),
        ],
        '200': [
          ApiResponse<int>('timestamp_start', def: 12345),
          ApiResponse<bool>('success', def: true),
          ApiResponse<List<ServiceModel>>(
            'data',
            def: [await ServiceModel().toParams()],
          ),
          ApiResponse<Map>('pagination', def: {}),
        ],
      },
    );
  }

  static Future<ApiDoc> oneService() async {
    return ApiDoc(
      summary: 'Get One Service',
      description: 'Retrieve a single service by ID',
      parameters: [
        ApiParameter('id', paramIn: ParamIn.path, description: 'Service ID'),
      ],
      response: {
        '403': [ApiResponse<String>('error', def: 'Unauthorized')],
        '404': [ApiResponse<String>('error', def: 'Service not found')],
        '200': [
          ApiResponse<int>('timestamp_start', def: 12345),
          ApiResponse<bool>('success', def: true),
          ApiResponse<ServiceModel>(
            'data',
            def: await ServiceModel().toParams(),
          ),
        ],
      },
    );
  }
}

Using Legacy ApiDoc with Routes

WebRoute(
  path: 'services',
  methods: RequestMethods.ONLY_GET,
  rq: rq,
  index: serviceController.serviceList,
  apiDoc: await ApiDocuments.serviceList(),
  permissions: ['admin'],
  auth: authController,
),

Authentication Documentation

Document your authentication schemes:

server.enableOpenAPI(
  // ... other config
  securitySchemes: {
    'bearerAuth': OpenAPISecurityScheme(
      type: 'http',
      scheme: 'bearer',
      bearerFormat: 'JWT',
      description: 'JWT token authentication',
    ),
    'apiKey': OpenAPISecurityScheme(
      type: 'apiKey',
      name: 'X-API-Key',
      location: 'header',
      description: 'API key for authentication',
    ),
  },
  security: [
    {'bearerAuth': []},
  ],
);

Exposing Documentation Endpoint

Create a custom documentation endpoint:

WebRoute(
  path: 'documentation',
  extraPath: ['api/docs'],
  rq: rq,
  methods: RequestMethods.ONLY_GET,
  index: () async {
    // Custom documentation rendering logic
    return rq.renderWidget('api_docs', {
      'title': 'API Documentation',
      'version': '1.0.0',
    });
  },
),

Best Practices

Documentation Guidelines

  1. Be Descriptive: Provide clear, detailed descriptions for all operations
  2. Use Examples: Include realistic examples in your schemas
  3. Document Errors: Document all possible error responses
  4. Consistent Structure: Use consistent naming and structure
  5. Keep Updated: Ensure documentation stays in sync with code

Schema Design

  1. Reusable Components: Create reusable schema components
  2. Validation Rules: Include validation constraints
  3. Nested Objects: Properly structure relationships
  4. Enum Values: Document all possible enum values

Security Documentation

  1. Auth Methods: Document all authentication methods
  2. Permissions: Clearly define required permissions
  3. Rate Limits: Document any rate limiting
  4. CORS: Document CORS configuration

Advanced Features

Custom Swagger UI

server.enableOpenAPI(
  // ... other config
  swaggerUIConfig: SwaggerUIConfig(
    title: 'My API Documentation',
    customCSS: '''
      .swagger-ui .topbar { 
        background-color: #2c3e50; 
      }
    ''',
  ),
);

Multiple API Versions

// Version 1
WebRoute(
  path: 'api/v1/services',
  // ... config
),

// Version 2
WebRoute(
  path: 'api/v2/services',
  // ... config with different documentation
),

Next Steps