Flurl

Guide to using Flurl, a fluent URL builder and HTTP client library for .NET

Flurl Repo stars is an elegant HTTP client library that combines the simplicity of string-based URL manipulation with the power of a full-featured HTTP client. Its name is a portmanteau of “Fluent” and “URL,” reflecting its core design philosophy.

Flurl consists of two main packages:

  • Flurl: The core URL builder with fluent syntax for constructing and manipulating URLs
  • Flurl.Http: Extensions that add HTTP capabilities, built on top of HttpClient

Unlike traditional REST clients that require separate client objects, Flurl takes a unique approach by extending the string type to allow any URL (represented as a string) to be the starting point for an HTTP request. This enables an extremely concise and chainable API that many developers find intuitive and readable.

InfoLink
LicenseGitHub (MIT)
DownloadsNuget
Latest VersionGitHub release (latest by date)
IssuesGitHub issues
ContributorsGitHub contributors

Key Features

Flurl offers several distinctive features that set it apart from other HTTP client libraries:

  • Fluent URL Building: Construct and manipulate URLs with a chainable, readable syntax
  • String Extension Methods: Start an HTTP request directly from any string or string variable
  • Automatic JSON Serialization: Built-in handling of JSON request and response bodies
  • Testing Support: Rich testing capabilities to simulate HTTP responses without actual network calls
  • Exception Handling: Detailed exceptions with access to the underlying HTTP response
  • Authentication: Built-in support for common authentication schemes
  • Testability: Powerful request testing capabilities with no mocking framework required
  • Lightweight: Minimal dependencies and overhead

Basic Usage

The following examples demonstrate Flurl’s concise syntax for common HTTP operations:

// Setup base URL
string apiUrl = "https://api.example.com";

// GET - Retrieve a single resource
TaskApiModel? task = await $"{apiUrl}/tasks/123".GetJsonAsync<TaskApiModel?>();

// GET - Retrieve a collection of resources
List<TaskApiModel> tasks = await $"{apiUrl}/tasks".GetJsonAsync<List<TaskApiModel>>();

// POST - Create a new resource
TaskApiCreateModel newTask = new TaskApiCreateModel { Title = "Buy flowers" };
TaskApiModel addedTask = await $"{apiUrl}/tasks"
    .PostJsonAsync(newTask)
    .ReceiveJson<TaskApiModel>();

// PUT - Update an existing resource
TaskApiUpdateModel updateModel = new TaskApiUpdateModel { Title = "Buy roses", Completed = true };
TaskApiModel updatedTask = await $"{apiUrl}/tasks/123"
    .PutJsonAsync(updateModel)
    .ReceiveJson<TaskApiModel>();
    
// DELETE - Remove a resource
await $"{apiUrl}/tasks/123".DeleteAsync();

// HEAD - Check if resource exists (returns true if status 200-299)
bool exists = await $"{apiUrl}/tasks/123".HeadAsync().ReceiveString().ContinueWith(t => !t.IsFaulted);

Advanced Features

URL Building

Flurl’s URL building capabilities are powerful and flexible:

// Query parameters
var result = await "https://api.example.com/search"
    .SetQueryParam("q", "search term")
    .SetQueryParam("category", "books")
    .GetJsonAsync<SearchResult>();
    
// Multiple query parameters at once
var result = await "https://api.example.com/search"
    .SetQueryParams(new 
    { 
        q = "search term",
        category = "books",
        maxResults = 50,
        format = "detailed"
    })
    .GetJsonAsync<SearchResult>();

// URL path segments
var user = await "https://api.example.com"
    .AppendPathSegment("users")
    .AppendPathSegment(userId)
    .GetJsonAsync<UserModel>();

Headers and Authentication

Flurl provides several ways to set headers and handle authentication:

// Setting headers
var response = await "https://api.example.com/tasks"
    .WithHeader("User-Agent", "MyApp/1.0")
    .WithHeader("Accept-Language", "en-US")
    .GetJsonAsync<List<TaskApiModel>>();
    
// Basic authentication
var response = await "https://api.example.com/secure"
    .WithBasicAuth("username", "password")
    .GetJsonAsync<SecureResource>();
    
// OAuth bearer token
var response = await "https://api.example.com/secure"
    .WithOAuthBearerToken("your-access-token")
    .GetJsonAsync<SecureResource>();

Error Handling

Flurl provides rich exception handling capabilities:

try {
    var task = await "https://api.example.com/tasks/999".GetJsonAsync<TaskApiModel>();
}
catch (FlurlHttpException ex) {
    var statusCode = ex.StatusCode;
    var errorBody = await ex.GetResponseJsonAsync<ErrorResponse>();
    
    if (statusCode == 404) {
        Console.WriteLine($"Task not found: {errorBody.Message}");
    }
    else if (statusCode == 401) {
        Console.WriteLine("Authentication required");
    }
    else {
        Console.WriteLine($"Error {statusCode}: {errorBody.Message}");
    }
}

Testing

Flurl’s testing capabilities allow for easy HTTP request simulation:

// Set up test response
"https://api.example.com/tasks"
    .WithHeader("Authorization", "Bearer token123")
    .HttpTest()
    .RespondWithJson(new [] { 
        new TaskApiModel { Id = 1, Title = "Task 1" },
        new TaskApiModel { Id = 2, Title = "Task 2" }
    });

// Execute code that makes the request
var tasks = await taskService.GetAllTasks();

// Verify request was made correctly
"https://api.example.com/tasks"
    .WithHeader("Authorization", "Bearer token123")
    .HttpTest()
    .ShouldHaveCalled();

// Clean up
HttpTest.Current.Dispose();

Structured API Client

While Flurl’s string extension approach is powerful, for larger applications it’s often better to create a structured API client:

public class TaskApiClient
{
    private readonly string _baseUrl;
    
    public TaskApiClient(string baseUrl)
    {
        _baseUrl = baseUrl;
        
        // Configure defaults for all requests
        FlurlHttp.ConfigureClient(_baseUrl, cli => 
            cli.WithHeader("User-Agent", "TaskApiClient/1.0")
                .WithTimeout(TimeSpan.FromSeconds(30)));
    }
    
    public Task<TaskApiModel> GetTaskAsync(int id) =>
        $"{_baseUrl}/tasks/{id}".GetJsonAsync<TaskApiModel>();
        
    public Task<List<TaskApiModel>> GetAllTasksAsync() =>
        $"{_baseUrl}/tasks".GetJsonAsync<List<TaskApiModel>>();
        
    public Task<TaskApiModel> CreateTaskAsync(TaskApiCreateModel task) =>
        $"{_baseUrl}/tasks"
            .PostJsonAsync(task)
            .ReceiveJson<TaskApiModel>();
            
    public Task<TaskApiModel> UpdateTaskAsync(int id, TaskApiUpdateModel task) =>
        $"{_baseUrl}/tasks/{id}"
            .PutJsonAsync(task)
            .ReceiveJson<TaskApiModel>();
            
    public Task DeleteTaskAsync(int id) =>
        $"{_baseUrl}/tasks/{id}".DeleteAsync();
}

// Usage
var client = new TaskApiClient("https://api.example.com");
var allTasks = await client.GetAllTasksAsync();

Comparison

Pros

  • Extremely intuitive and readable syntax
  • Minimal code required for simple API operations
  • Powerful URL building with chainable methods
  • Excellent testability through the built-in HttpTest framework
  • Supports both quick one-liners and structured API clients
  • Active development and maintenance with good community support
  • Lightweight with minimal dependencies
  • MIT licensed with no usage restrictions

Cons

  • Performance overhead from string manipulations and extension method chains
  • Memory allocations can be higher than direct HttpClient usage
  • String extension approach may be considered an anti-pattern by some developers
  • Can encourage scattered HTTP calls throughout codebase if not properly structured
  • Less suitable for extremely complex API interactions with custom requirements
  • Not as feature-rich as full REST client frameworks

When to Choose Flurl

Flurl is particularly well-suited for:

  1. Rapid prototyping and quick API integrations
  2. Small to medium-sized projects where developer productivity is prioritized
  3. API exploration during development stages
  4. Applications with moderate API requirements that benefit from concise syntax
  5. Projects where testability of HTTP interactions is important

For large enterprise applications or performance-critical systems, consider whether the convenience of Flurl outweighs potential performance considerations.

Full Sample

See the full sample on GitHub: https://github.com/BenjaminAbt/dotnet.rest-samples