Skip to content
AgileLogics
6 min read

Projects and NuGet Packages

Prerequisites: Modules and namespaces

Scripts are great for exploring; applications and libraries live in projects, built by the dotnet command-line tool.

Creating a solution

dotnet new sln -n Shop
dotnet new classlib -lang F# -o src/Shop.Core
dotnet new console -lang F# -o src/Shop.Cli
dotnet new xunit -lang F# -o tests/Shop.Tests
dotnet sln add src/Shop.Core src/Shop.Cli tests/Shop.Tests
dotnet add src/Shop.Cli reference src/Shop.Core
dotnet add tests/Shop.Tests reference src/Shop.Core

This gives a class library for your domain code, a console app that uses it, and a test project — a common layout for real F# work.

The .fsproj file

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Money.fs" />
    <Compile Include="Orders.fs" />
    <Compile Include="Library.fs" />
  </ItemGroup>

</Project>

Unlike C#, F# lists every source file explicitly, in compilation order: Orders.fs can use Money.fs, but not the other way round. Editors keep this list for you ("Move file up/down" in Ionide and Rider). This rule prevents circular dependencies and makes a codebase easy to read from top to bottom.

NuGet packages

NuGet is .NET's package manager, with hundreds of thousands of packages at nuget.org:

dotnet add package FSharp.Data
dotnet add package FsToolkit.ErrorHandling
dotnet list package
dotnet restore

dotnet add package adds a <PackageReference> line to the .fsproj. Every C# library works from F#, plus F#-specific ones such as FSharp.Data (type providers), FsToolkit.ErrorHandling (result helpers), Argu (command-line parsing), Thoth.Json and FSharp.SystemTextJson (JSON), Expecto (tests), and Giraffe (web).

Packages in scripts

Scripts can reference packages directly — no project needed:

// illustrative: downloads the package on first run
#r "nuget: Humanizer"

open Humanizer

printfn "%s" ("some_database_column".Humanize())
printfn "%s" (System.TimeSpan.FromMinutes(90.0).Humanize(2))
Some database column
1 hour, 30 minutes

Pin a version with #r "nuget: Humanizer, 2.14.1" so the script keeps working the same way. #load "Helpers.fsx" includes another script file.

Everyday commands

Command Does
dotnet build compile (Debug by default)
dotnet run build and run the console project in this folder
dotnet watch run rebuild and restart on every save
dotnet test build and run test projects
dotnet publish -c Release produce deployable output
dotnet tool install fantomas install a tool (after dotnet new tool-manifest)

Paket is an alternative dependency manager popular in parts of the F# community; plain NuGet with PackageReference is the default and works well for most projects.