Flurl
5 minute read
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.
Info | Link |
---|---|
License | |
Downloads | |
Latest Version | |
Issues | |
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
- 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
- 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:
- Rapid prototyping and quick API integrations
- Small to medium-sized projects where developer productivity is prioritized
- API exploration during development stages
- Applications with moderate API requirements that benefit from concise syntax
- 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