{"id":89245,"date":"2020-12-07T16:05:06","date_gmt":"2020-12-07T16:05:06","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=89245"},"modified":"2022-04-24T21:25:22","modified_gmt":"2022-04-24T21:25:22","slug":"creating-your-first-crud-app-with-suave-and-f","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/creating-your-first-crud-app-with-suave-and-f\/","title":{"rendered":"Creating your first CRUD app with Suave and F#"},"content":{"rendered":"<p>F# is the go-to language if you\u2019re seeking functional programming within the .NET world. It is multi-paradigm, flexible, and provides smooth interoperability with C#, which brings even more power to your development stack, but did you know that you can build APIs with F#? Not common, I know, but it\u2019s possible due to the existence of frameworks like <a href=\"https:\/\/suave.io\/\">Suave.io<\/a>.<\/p>\n<p>Suave is a lightweight, non-blocking web server. Since it is non-blocking, it means you can create scalable applications that perform way faster than the ordinary APIs. The whole framework was built as a non-blocking organism.<\/p>\n<p>Inspired by <a href=\"http:\/\/happstack.com\/page\/view-page-slug\/1\/happstack\">Happstack<\/a>, it aims to embed web server capabilities into applications by providing support to components and services like Websockets, HTTPS, multiple TCP\/IP bindings, Basic Access Authentication, Keep-Alive, HTTP compression, and many more.<\/p>\n<p>In this article, you\u2019ll be driven through the Suave server by developing a complete CRUD REST API.<\/p>\n<h2>Setting Up<\/h2>\n<p>Suave can be installed via NuGet Manager. However, before you can do it, you need to create a project in your Visual Studio Community Edition.<\/p>\n<p>First, make sure you have the .NET Core SDK installed. If not, go ahead and <a href=\"https:\/\/dotnet.microsoft.com\/download\">install it<\/a>.<\/p>\n<p>Then, open Visual Studio, go to the \u201cCreate a new project\u201d window, and filter for F# applications, and select the option \u201cConsole App (.NET Core)\u201d as shown in Figure 1. Click <em>Next<\/em>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"850\" class=\"wp-image-89246\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image.png\" \/><\/p>\n<p><strong>Figure 1.<\/strong> Creating a new F# project.<\/p>\n<p>The following window will ask for a project and solution name, as well as the folder destination. Fill the fields according to Figure 2 and click <em>Create<\/em>. <img loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"850\" class=\"wp-image-89247\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-1.png\" \/><\/p>\n<p><strong>Figure 2.<\/strong> Providing a project and solution name.<\/p>\n<p>Once the project creation finishes, you\u2019ll be able to see that only one file comes with it: the <em>Program.fs<\/em>. Within it, there\u2019s a Hello World example in F#.<\/p>\n<p>That\u2019s a very basic structure of an F# program, but this example won\u2019t use any of it.<a id=\"post-89245-_heading=h.1fob9te\"><\/a><\/p>\n<h2>Installing Suave<\/h2>\n<p>Before going any further, you need to set up Suave properly. The <a href=\"https:\/\/suave.io\/\">usual method<\/a> recommends doing it via <a href=\"https:\/\/github.com\/fsprojects\/Paket\">Paket<\/a>, however, since you\u2019re already within the Visual Studio environment, stick to NuGet.<\/p>\n<p>Right-click the solution and select \u201cManage NuGet Packages for Solution&#8230;\u201d and browse for Suave at the search box.<\/p>\n<p>Select it according to Figure 3 and click the <em>Install<\/em> button.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1155\" height=\"801\" class=\"wp-image-89248\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-2.png\" \/><\/p>\n<p><strong>Figure 3.<\/strong> Installing Suave at NuGet.<\/p>\n<p>For the API construction, you\u2019ll also need Newtonsoft\u2019s JSON package, as it provides a handful of auxiliary methods to deal with conversions from object to JSON and vice versa.<\/p>\n<p>Follow Figure 4 instructions to install it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1190\" height=\"813\" class=\"wp-image-89249\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-3.png\" \/><\/p>\n<p><strong>Figure 4.<\/strong> Installing Newtonsoft.Json dependency.<\/p>\n<h2>The Project Structure<\/h2>\n<p>Great! Now move on to the project building. You noticed that you already have a <em>Program.fs<\/em> file. You\u2019ll use it as the main execution file. However, two other files are needed: one for the in-memory database operations, and the other for the service operations.<\/p>\n<p>Go ahead and create both of them according to Figures 5 and 6 below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"941\" height=\"653\" class=\"wp-image-89250\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-4.png\" \/><\/p>\n<p><strong>Figure 5.<\/strong> Creating the user\u2019s repository.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"941\" height=\"653\" class=\"wp-image-89251\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-5.png\" \/><\/p>\n<p><strong>Figure 6.<\/strong> Creating the user\u2019s service.<\/p>\n<h2>The Repository<\/h2>\n<p>First, start coding the repository since it\u2019s the basis for the rest of the API. Take the code from Listing 1 and paste it into the <em>UserRepository.fs<\/em> file.<\/p>\n<p><strong>Listing 1.<\/strong> The user\u2019s repository code.<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace SuaveAPI.UserRepository\r\nopen System.Collections.Generic\r\ntype User =\r\n    { UserId: int\r\n      Name: string\r\n      Age: int\r\n      Address: string\r\n      Salary: double }\r\nmodule UserRepository =\r\n    let users = new Dictionary&lt;int, User&gt;()\r\n    let getUsers () = users.Values :&gt; seq&lt;User&gt;\r\n    let getUser id =\r\n        if users.ContainsKey(id) then Some users.[id] else None\r\n    let createUser user =\r\n        let id = users.Values.Count + 1\r\n        let newUser = { user with UserId = id }\r\n        users.Add(id, newUser)\r\n        newUser\r\n    let updateUserById id userToUpdate =\r\n        if users.ContainsKey(id) then\r\n            let updatedUser = { userToUpdate with UserId = id }\r\n            users.[id] &lt;- updatedUser\r\n            Some updatedUser\r\n        else\r\n            None\r\n    let updateUser userToUpdate =\r\n        updateUserById userToUpdate.UserId userToUpdate\r\n    let deleteUser id = users.Remove(id) |&gt; ignore<\/pre>\n<p>For the sake of simplicity, this project won\u2019t make use of any physical database, so the user\u2019s data will be stored in an in-memory Dictionary called users.<\/p>\n<p>The dictionary\u2019s keys refer to each user\u2019s id, while the values represent the user objects.<\/p>\n<p>The full repository is made of six main operations:<\/p>\n<ul>\n<li><em>getUsers<\/em>: take the dictionary and translates it into an F# sequence.<\/li>\n<li><em>getUser<\/em>: the method will search the dictionary for one specific user based on its id.<\/li>\n<li><em>createUser<\/em>: creates a new user object, certifying that the id is always going to be replaced with an auto-incremented value.<\/li>\n<li><em>updateUserById\/updateUser<\/em>: to update a user, you first need to make sure the passed id is valid and belongs to a real user. Then, call the updateUser method which will, in turn, updates the user on the dictionary.<\/li>\n<li><em>deleteUser<\/em>: simply removes the user based on its id.<\/li>\n<\/ul>\n<h2>The Service<\/h2>\n<p>Now, head to the service class. Open it and add the Listing 2 contents to it.<\/p>\n<p><strong>Listing 2.<\/strong> User\u2019s service code.<\/p>\n<pre class=\"lang:c# theme:vs2012 \">namespace SuaveAPI.UserService\r\nopen Newtonsoft.Json\r\nopen Newtonsoft.Json.Serialization\r\nopen Suave\r\nopen Suave.Operators\r\nopen Suave.Successful\r\n[&lt;AutoOpen&gt;]\r\nmodule UserService =\r\n    open Suave.RequestErrors\r\n    open Suave.Filters\r\n    \/\/ auxiliary methods\r\n    let getUTF8 (str: byte []) = System.Text.Encoding.UTF8.GetString(str)\r\n    let jsonToObject&lt;'t&gt; json =\r\n        JsonConvert.DeserializeObject(json, typeof&lt;'t&gt;) :?&gt; 't\r\n    \/\/ 't -&gt; WebPart\r\n    let JSON v =\r\n        let jsonSerializerSettings = new JsonSerializerSettings()\r\n        jsonSerializerSettings.ContractResolver \r\n          &lt;- new CamelCasePropertyNamesContractResolver()\r\n        JsonConvert.SerializeObject(v, jsonSerializerSettings)\r\n        |&gt; OK\r\n        &gt;=&gt; Writers.setMimeType \"application\/json\"\r\n    type Actions&lt;'t&gt; =\r\n        { ListUsers: unit -&gt; 't seq\r\n          GetUser: int -&gt; 't option\r\n          AddUser: 't -&gt; 't\r\n          UpdateUser: 't -&gt; 't option\r\n          UpdateUserById: int -&gt; 't -&gt; 't option\r\n          DeleteUser: int -&gt; unit }\r\n    let getActionData&lt;'t&gt; (req: HttpRequest) =\r\n        req.rawForm |&gt; getUTF8 |&gt; jsonToObject&lt;'t&gt;\r\n    let handle nameOfAction action =\r\n        let badRequest =\r\n            BAD_REQUEST \"Oops, something went wrong here!\"\r\n        let notFound = NOT_FOUND \"Oops, I couldn't find that!\"\r\n        let handleAction reqError =\r\n            function\r\n            | Some r -&gt; r |&gt; JSON\r\n            | _ -&gt; reqError\r\n        let listAll =\r\n            warbler (fun _ -&gt; action.ListUsers() |&gt; JSON)\r\n        let getById = action.GetUser &gt;&gt; handleAction notFound\r\n        let updateById id =\r\n            request\r\n                (getActionData\r\n                 &gt;&gt; (action.UpdateUserById id)\r\n                 &gt;&gt; handleAction badRequest)\r\n        let deleteById id =\r\n            action.DeleteUser id\r\n            NO_CONTENT\r\n        let actionPath = \"\/\" + nameOfAction\r\n        \/\/ path's mapping\r\n        choose [ path actionPath\r\n                 &gt;=&gt; choose [ GET &gt;=&gt; listAll\r\n                              POST\r\n                              &gt;=&gt; request (getActionData \r\n                              &gt;&gt; action.AddUser &gt;&gt; JSON)\r\n                              PUT\r\n                              &gt;=&gt; request\r\n                                      (getActionData\r\n                                       &gt;&gt; action.UpdateUser\r\n                                       &gt;&gt; handleAction badRequest) ]\r\n                 DELETE &gt;=&gt; pathScan \"\/users\/%d\" \r\n                       (fun id -&gt; deleteById id)\r\n                 GET &gt;=&gt; pathScan \"\/users\/%d\" (fun id -&gt; getById id)\r\n                 PUT &gt;=&gt; pathScan \"\/users\/%d\" \r\n                  (fun id -&gt; updateById id) ]<\/pre>\n<p>Note that the namespace at the beginning of the file is very important to make the modules available to one another. The AutoOpen annotation above the module declaration helps to expose the let-bound values of our Actions type. However, if you don\u2019t want to use the annotation, you can remove it and directly call the <em>Actions<\/em> type via the open command.<\/p>\n<p>The services count on two auxiliary methods: one for extracting the UTF-8 value of a string, and the other for converting JSON to F# objects.<\/p>\n<p>The <a href=\"https:\/\/suave.io\/routing.html\">WebPart<\/a> config is essential. A WebPart function returns an asynchronous workflow which itself ultimately returns an <em>HttpContext<\/em> option. It encapsulates both request and response models and simplifies their usage, like setting the <em>Content-Type<\/em> of our responses, for example.<\/p>\n<p>The Actions resource works as a container for all the API operations. This representation is excellent because it allows porting any API methods to it. If you have other domains for your API (like Accounts, Students, Sales, etc.), you can map the endpoints within other Actions and use them right away.<\/p>\n<p>It all works due to the <em>handle<\/em> structure. It receives an action and its name and implicitly converts it to each service operation.<\/p>\n<p>Finally, the paths are mapped at the end of the listing, through Suave\u2019s <em>path<\/em> and <em>pathScan<\/em> features. They allow redirecting requests to specific methods, and scan path params (as you have with the update, get, and delete operations) to extract the values before processing the request.<\/p>\n<h2>The Program<\/h2>\n<p>So far, you\u2019ve built everything the API needs to work. Now, set up the main Program F# file. For this, open the <em>Program.fs<\/em> and add the content presented by Listing 3. You\u2019ll get a few errors, but they\u2019ll go away when you run the program.<\/p>\n<p><strong>Listing 3.<\/strong> Main F# file code.<\/p>\n<pre class=\"lang:c# theme:vs2012\">namespace SuaveAPI\r\nmodule Program =\r\n    open Suave.Web\r\n    open SuaveAPI.UserService\r\n    open SuaveAPI.UserRepository\r\n    [&lt;EntryPoint&gt;]\r\n    let main argv =\r\n        let userActions =\r\n            handle\r\n                \"users\"\r\n                { ListUsers = UserRepository.getUsers\r\n                  GetUser = UserRepository.getUser\r\n                  AddUser = UserRepository.createUser\r\n                  UpdateUser = UserRepository.updateUser\r\n                  UpdateUserById = UserRepository.updateUserById\r\n                  DeleteUser = UserRepository.deleteUser }\r\n        startWebServer defaultConfig userActions\r\n        0<\/pre>\n<p>This one resembles a bit the previous content of <em>Program.fs<\/em>. Suave\u2019s server is always started the same way, through the startWebServer method.<\/p>\n<p>The method receives two arguments:<\/p>\n<ul>\n<li>The server\u2019s config object. If you want to go raw, just provide it with the default <em>dafaultConfig<\/em> object.<\/li>\n<li>And the WebPart mentioned before.<\/li>\n<\/ul>\n<p>The WebPart is just a representation of the Actions created within the UserService file. Make sure to call each one of the service methods accordingly.<\/p>\n<p>The code must always end with a 0. The zero says to Suave that the server must stop when you shut it down; otherwise, it\u2019ll keep running forever and locking that port.<\/p>\n<h2>Testing<\/h2>\n<p>Now it\u2019s time to test the API. For this, you\u2019ll make use of <a href=\"https:\/\/www.postman.com\/\">Postman<\/a>, a great tool for API testing. Download and install it if you still don\u2019t have it.<\/p>\n<p>Then, get back to your application and execute it by hitting the <em>Start<\/em> button (Figure 7) or pressing <em>F5<\/em>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"375\" height=\"38\" class=\"wp-image-89252\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-6.png\" \/><\/p>\n<p><strong>Figure 7.<\/strong> Starting the application up.<\/p>\n<p>It will prompt a window stating that the Suave listener has started at a specific address + port, as shown below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"580\" height=\"195\" class=\"wp-image-89253\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-7.png\" \/><\/p>\n<p><strong>Figure 8.<\/strong> App successfully started.<\/p>\n<p>Since there\u2019s no user registered yet, you need to create one first. Within Postman, open a new window, and fill it in according to Figure 9.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1157\" height=\"402\" class=\"wp-image-89254\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-8.png\" \/><\/p>\n<p><strong>Figure 9.<\/strong> Creating a new user with Postman.<\/p>\n<p>Make sure to select the proper HTTP verb (<em>POST<\/em>), and the option \u201c<em>raw<\/em>\u201d at the <em>Body<\/em> tab, as well as JSON as the data type. Provide a JSON with the user\u2019s data, as shown in the figure and hit the <em>Send<\/em> button.<\/p>\n<p>If everything goes well, you may see the newly created user coming back within the response. Now, try retrieving the same user through the GET demonstrated in Figure 10.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1160\" height=\"711\" class=\"wp-image-89255\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/12\/word-image-9.png\" \/><\/p>\n<p><strong>Figure 10.<\/strong> Retrieving the newly created user.<\/p>\n<h2>Conclusion<\/h2>\n<p>As a homework task, I\u2019d ask you to test the rest of the CRUD operations and check if everything\u2019s working fine. It\u2019d be also great to substitute the in-memory dictionary as the database for a real one, like MySQL or SQL Server. This way, you can understand better how F# and Suave communicate with real databases.<\/p>\n<p>Plus, make sure to refer to the <a href=\"https:\/\/suave.io\/\">official docs<\/a> for more on what you can do with this amazing web server.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Suave is an easy to use framework for F#. In this article, Diogo Souza explains how to create an F# CRUD app with Suave.&hellip;<\/p>\n","protected":false},"author":320401,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[5134],"coauthors":[60461],"class_list":["post-89245","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89245","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/320401"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=89245"}],"version-history":[{"count":3,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89245\/revisions"}],"predecessor-version":[{"id":89257,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/89245\/revisions\/89257"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=89245"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=89245"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=89245"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=89245"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}