Isaac.

api-development

API Versioning Strategies

Design APIs that evolve while maintaining backward compatibility.

By Emem IsaacMay 22, 20213 min read
#api versioning#compatibility#evolution#backward compatibility
Share:

A Simple Analogy

API versioning is like a company's product line. As products improve, you maintain older versions for existing customers while offering newer versions. You can't force everyone to upgrade simultaneously.


Why Version APIs?

  • Evolution: APIs must change to add features
  • Compatibility: Existing clients shouldn't break
  • Gradual migration: Time for clients to upgrade
  • Support: Maintain old versions for existing users
  • Flexibility: Different clients use different versions

Versioning Strategies

URL Path Versioning

GET /api/v1/users        # Version 1
GET /api/v2/users        # Version 2
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers() => Ok(new[] { "v1" });
}

Query Parameter Versioning

GET /api/users?version=1
GET /api/users?version=2
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers([FromQuery] int version = 1)
    {
        return version switch
        {
            1 => Ok(new[] { "name", "email" }),
            2 => Ok(new[] { "id", "name", "email", "phone" }),
            _ => BadRequest("Unsupported version")
        };
    }
}

Header Versioning

GET /api/users
X-API-Version: 1
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers([FromHeader(Name = "X-API-Version")] int version = 1)
    {
        return version switch
        {
            1 => Ok(GetUsersV1()),
            2 => Ok(GetUsersV2()),
            _ => BadRequest("Unsupported version")
        };
    }
}

Media Type Versioning

GET /api/users
Accept: application/vnd.company.user-v1+json

Deprecation Strategy

[Obsolete("Use v2 instead")]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersControllerV1 : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] { new { Name = "Alice" } });
    }
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersControllerV2 : ControllerBase
{
    [HttpGet]
    public IActionResult GetUsers()
    {
        return Ok(new[] { 
            new { Id = 1, Name = "Alice", Email = "alice@example.com" } 
        });
    }
}

Practical Example

// V1: Simple user response
public class UserDtoV1
{
    public string Name { get; set; }
    public string Email { get; set; }
}

// V2: Extended with additional fields
public class UserDtoV2
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public string Status { get; set; }
}

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    [MapToApiVersion("1.0")]
    public async Task<ActionResult<UserDtoV1>> GetUserV1(int id)
    {
        var user = await _userService.GetUserAsync(id);
        return Ok(new UserDtoV1 
        { 
            Name = user.Name, 
            Email = user.Email 
        });
    }
    
    [HttpGet("{id}")]
    [MapToApiVersion("2.0")]
    public async Task<ActionResult<UserDtoV2>> GetUserV2(int id)
    {
        var user = await _userService.GetUserAsync(id);
        return Ok(new UserDtoV2 
        { 
            Id = user.Id,
            Name = user.Name, 
            Email = user.Email,
            CreatedAt = user.CreatedAt,
            Status = user.Status
        });
    }
}

Sunset Policy

Version 1: Active until Dec 31, 2024
Version 2: Supported for 2 years after release
Version 3: Current stable version

Timeline:
- V1: Deprecated (Oct 2024), Sunset (Dec 31, 2024)
- V2: Stable, 18 months remaining
- V3: Current, fully supported

Best Practices

  1. Plan ahead: Design versioning from day one
  2. Semantic versioning: Major.Minor.Patch
  3. Deprecation warnings: Notify clients early
  4. Sunset timeline: Clear migration path
  5. Documentation: Document all versions

Related Concepts to Explore

  • API gateway versioning
  • Content negotiation
  • Breaking vs. non-breaking changes
  • Schema evolution
  • Backward compatibility testing

Summary

API versioning enables controlled evolution while maintaining backward compatibility. Choose a strategy, communicate clearly, and provide migration paths for smooth client upgrades.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours