DEV Community

IronSoftware
IronSoftware

Posted on

How to Use curl in .NET (F#)

How to Use curl in .NET (F#)

Comprehensive library now available open source to allow the Userland Curl feature from Linux available in F# and .NET 10

F# developers can also use CurlDotNet to execute curl commands directly in their functional code. The library works seamlessly with F#'s async workflows and type system.

Installing CurlDotNet

Add the NuGet package to your F# project:

dotnet add package CurlDotNet
Enter fullscreen mode Exit fullscreen mode

Or in your .fsproj:

<ItemGroup>
  <PackageReference Include="CurlDotNet" Version="1.0.0" />
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Basic Usage in F

Here's a simple example in F#:

open CurlDotNet
open System
open System.Threading.Tasks

let main argv =
    async {
        let! result = Curl.ExecuteAsync("curl https://api.github.com/users/octocat") |> Async.AwaitTask

        printfn "Status: %d" result.StatusCode
        printfn "Body: %s" result.Body
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Using F# Async Workflows

F#'s async workflows work naturally with CurlDotNet:

open CurlDotNet
open System
open System.Threading.Tasks

let fetchUser userId =
    async {
        let command = sprintf "curl https://api.github.com/users/%s" userId
        let! result = Curl.ExecuteAsync(command) |> Async.AwaitTask

        if result.IsSuccess then
            return Some result.Body
        else
            return None
    }

let main argv =
    async {
        match! fetchUser "octocat" with
        | Some body -> printfn "Success: %s" body
        | None -> printfn "Failed to fetch user"
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Working with JSON

F# can work with JSON responses using standard .NET JSON libraries:

open CurlDotNet
open System.Text.Json

type User = {
    Login: string
    Name: string
    PublicRepos: int
}

let fetchUser userId =
    async {
        let command = sprintf "curl https://api.github.com/users/%s" userId
        let! result = Curl.ExecuteAsync(command) |> Async.AwaitTask

        if result.IsSuccess then
            let user = JsonSerializer.Deserialize<User>(result.Body)
            return Some user
        else
            return None
    }

let main argv =
    async {
        match! fetchUser "octocat" with
        | Some user ->
            printfn "User: %s" user.Name
            printfn "Repos: %d" user.PublicRepos
        | None ->
            printfn "Failed"
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

POST Requests

Here's how to make POST requests in F#:

open CurlDotNet
open System.Text.Json

let createPost title body userId =
    async {
        let command = sprintf @"
          curl -X POST https://jsonplaceholder.typicode.com/posts \
            -H 'Content-Type: application/json' \
            -d '{""title"":""%s"",""body"":""%s"",""userId"":%d}'
        " title body userId

        let! result = Curl.ExecuteAsync(command) |> Async.AwaitTask
        return result
    }

let main argv =
    async {
        let! result = createPost "My Post" "This is content" 1

        if result.IsSuccess then
            printfn "Created post with status: %d" result.StatusCode
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Error Handling

F#'s pattern matching makes error handling elegant:

open CurlDotNet
open CurlDotNet.Exceptions

let safeExecute command =
    async {
        try
            let! result = Curl.ExecuteAsync(command) |> Async.AwaitTask
            return Ok result
        with
        | :? CurlDnsException as ex -> return Error $"DNS error: {ex.Message}"
        | :? CurlTimeoutException as ex -> return Error $"Timeout: {ex.Message}"
        | :? CurlHttpException as ex -> return Error $"HTTP {ex.StatusCode}: {ex.Message}"
        | :? CurlException as ex -> return Error $"Error: {ex.Message}"
    }

let main argv =
    async {
        match! safeExecute "curl https://api.github.com/users/octocat" with
        | Ok result ->
            printfn "Success: %s" result.Body
        | Error msg ->
            printfn "Failed: %s" msg
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Using Computation Expressions

You can create a custom computation expression for cleaner syntax:

open CurlDotNet
open System

type CurlBuilder() =
    member _.Yield(result: CurlResult) = result
    member _.Bind(result: Task<CurlResult>, f) = 
        async {
            let! res = Async.AwaitTask result
            return! f res |> Async.AwaitTask
        } |> Async.StartAsTask

    member _.Zero() = Task.FromResult(Unchecked.defaultof<CurlResult>)

let curl = CurlBuilder()

let fetchData =
    async {
        let! result = Curl.ExecuteAsync("curl https://api.github.com/users/octocat") |> Async.AwaitTask

        if result.IsSuccess then
            printfn "Data: %s" result.Body
    }
Enter fullscreen mode Exit fullscreen mode

Pipelining and Composition

F#'s pipelining works great with CurlDotNet:

open CurlDotNet

let fetchUser userId =
    sprintf "curl https://api.github.com/users/%s" userId
    |> Curl.ExecuteAsync
    |> Async.AwaitTask

let parseBody (result: CurlResult) =
    if result.IsSuccess then Some result.Body else None

let main argv =
    async {
        "octocat"
        |> fetchUser
        |> Async.RunSynchronously
        |> parseBody
        |> Option.iter (printfn "Body: %s")
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Real-World Example: GitHub API Client

Here's a complete F# example:

open CurlDotNet
open System
open System.Text.Json

type GitHubUser = {
    Login: string
    Name: string
    PublicRepos: int
    Followers: int
}

type Repository = {
    Name: string
    FullName: string
    Private: bool
    Description: string option
}

let githubApi token endpoint =
    async {
        let command = sprintf @"
          curl %s \
            -H 'Accept: application/vnd.github.v3+json' \
            -H 'Authorization: token %s'
        " endpoint token

        let! result = Curl.ExecuteAsync(command) |> Async.AwaitTask

        if result.IsSuccess then
            return Ok result.Body
        else
            return Error $"HTTP {result.StatusCode}"
    }

let getUser token =
    async {
        match! githubApi token "https://api.github.com/user" with
        | Ok body -> 
            let user = JsonSerializer.Deserialize<GitHubUser>(body)
            return Some user
        | Error _ -> return None
    }

let getRepos token =
    async {
        match! githubApi token "https://api.github.com/user/repos" with
        | Ok body ->
            let repos = JsonSerializer.Deserialize<Repository[]>(body)
            return Some repos
        | Error _ -> return None
    }

let main argv =
    let token = Environment.GetEnvironmentVariable("GITHUB_TOKEN")

    async {
        match! getUser token with
        | Some user ->
            printfn "Logged in as: %s" user.Login
            printfn "Name: %s" user.Name
            printfn "Public repos: %d" user.PublicRepos

            match! getRepos token with
            | Some repos ->
                printfn "\nRepositories:"
                repos
                |> Array.iter (fun repo -> printfn "  - %s" repo.FullName)
            | None -> printfn "Failed to fetch repos"
        | None -> printfn "Failed to authenticate"
    } |> Async.RunSynchronously

    0
Enter fullscreen mode Exit fullscreen mode

Conclusion

CurlDotNet works beautifully with F#'s functional programming style. The async workflows, pattern matching, and pipelining all make for clean, elegant code when working with HTTP requests.

Try it in your F# project - you'll find it fits naturally with F#'s idioms!


Learn More:

Top comments (0)