|
| 1 | +--- |
| 2 | +description: Use the Nitric framework to easily build and deploy C# REST APIs for AWS, Azure, or GCP |
| 3 | +title_seo: Building your first API with C# and Nitric |
| 4 | +tags: |
| 5 | + - API |
| 6 | + - Key Value Store |
| 7 | +languages: |
| 8 | + - csharp |
| 9 | +published_at: 2023-06-16 |
| 10 | +updated_at: 2025-01-06 |
| 11 | +--- |
| 12 | + |
| 13 | +# Building a REST API with Nitric |
| 14 | + |
| 15 | +This guide will show you how to build a serverless REST API with the Nitric framework using C#. The example API enables getting, setting, and editing basic user profile information using a Nitric [key value store](/keyvalue) to store user data. Once the API is created, we'll test it locally, then optionally deploy it to a cloud of your choice. |
| 16 | + |
| 17 | +The API will provide the following routes: |
| 18 | + |
| 19 | +| **Method** | **Route** | **Description** | |
| 20 | +| ---------- | -------------- | -------------------------------- | |
| 21 | +| `GET` | /profiles | Get all profiles | |
| 22 | +| `GET` | /profiles/[id] | Get a specific profile by its Id | |
| 23 | +| `POST` | /profiles | Create a new profile | |
| 24 | +| `DELETE` | /profiles/[id] | Delete a profile | |
| 25 | + |
| 26 | +There is also an extended section of the guide that adds file operations using a Nitric [bucket](/storage) to store and retrieve profile pictures. The extension adds these routes to the API: |
| 27 | + |
| 28 | +| **Method** | **Route** | **Description** | |
| 29 | +| ---------- | ----------------------------- | --------------------------------- | |
| 30 | +| `GET` | /profiles/[id]/image/upload | Get a profile image upload URL | |
| 31 | +| `GET` | /profiles/[id]/image/download | Get a profile image download URL | |
| 32 | +| `GET` | /profiles/[id]/image/view | View the image that is downloaded | |
| 33 | + |
| 34 | +## Prerequisites |
| 35 | + |
| 36 | +- [.NET SDK](https://dotnet.microsoft.com/en-us/download/dotnet) |
| 37 | +- The [Nitric CLI](/get-started/installation) |
| 38 | +- _(optional)_ Your choice of an [AWS](https://aws.amazon.com), [GCP](https://cloud.google.com), or [Azure](https://azure.microsoft.com) account |
| 39 | + |
| 40 | +## Getting started |
| 41 | + |
| 42 | +Let's start by creating a new project for our API: |
| 43 | + |
| 44 | +```bash |
| 45 | +nitric new MyProfileApi dotnet-starter |
| 46 | +cd MyProfileApi |
| 47 | +``` |
| 48 | + |
| 49 | +The scaffolded project should have the following structure: |
| 50 | + |
| 51 | +```text |
| 52 | +MyProfileApi/ |
| 53 | +├── services/Hello |
| 54 | +│ ├── Hello.cs |
| 55 | +│ ├── Hello.csproj |
| 56 | +dotnet.dockerfile |
| 57 | +dontnet.dockerfile.dockerignore |
| 58 | +nitric.yaml |
| 59 | +README.md |
| 60 | +``` |
| 61 | + |
| 62 | +## Building the API |
| 63 | + |
| 64 | +Let's start by adding the profile model. |
| 65 | + |
| 66 | +### Define the Profile Model |
| 67 | + |
| 68 | +Create a new file `Main/Profile.cs`: |
| 69 | + |
| 70 | +```csharp title:Main/Profile.cs |
| 71 | +namespace Common; |
| 72 | +public class Profile |
| 73 | +{ |
| 74 | + public string Name { get; set; } |
| 75 | + public int Age { get; set; } |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +### Define the API |
| 80 | + |
| 81 | +Applications built with Nitric can contain many APIs, let's start by adding an API and a key value store to this project to serve as the public endpoint. |
| 82 | + |
| 83 | +```csharp title:Main/Main.cs |
| 84 | +using Application = Nitric.Sdk.Nitric; |
| 85 | +using Nitric.Sdk.Resource; |
| 86 | +using Nitric.Sdk.Service; |
| 87 | +using System.Collections.Generic; |
| 88 | +using Common; |
| 89 | +using System; |
| 90 | + |
| 91 | +var profiles = Application.KV<Profile>("profiles").Allow( |
| 92 | + KeyValueStorePermission.Set, |
| 93 | + KeyValueStorePermission.Get, |
| 94 | + KeyValueStorePermission.Delete |
| 95 | +); |
| 96 | + |
| 97 | +var api = Application.Api("main"); |
| 98 | +``` |
| 99 | + |
| 100 | +Here we're creating an API named public and key value store named `profiles`, then requesting get and set permissions which allows our function to access the key value store. |
| 101 | + |
| 102 | +<Note> |
| 103 | + Resources in Nitric like `api` and `key-value store` represent high-level |
| 104 | + cloud resources. When your app is deployed Nitric automatically converts these |
| 105 | + requests into appropriate resources for the specific |
| 106 | + [provider](/docs/providers). Nitric also takes care of adding the IAM roles, |
| 107 | + policies, etc. that grant the requested access. For example the `key-value` |
| 108 | + store resource uses DynamoDB in AWS or FireStore on Google Cloud. |
| 109 | +</Note> |
| 110 | + |
| 111 | +### Create profiles with POST |
| 112 | + |
| 113 | +Next we will add features that allow our API consumers to work with profile data. |
| 114 | + |
| 115 | +<Note> |
| 116 | + You could separate some or all of these handlers into their own files if you |
| 117 | + prefer. For simplicity we'll group them together in this guide. |
| 118 | +</Note> |
| 119 | + |
| 120 | +```csharp title:Main/Main.cs |
| 121 | +api.Post("/profiles", ctx => |
| 122 | +{ |
| 123 | + var profile = ctx.Req.Json<Profile>(); |
| 124 | + var id = Guid.NewGuid().ToString(); |
| 125 | + profiles.Set(id, profile); |
| 126 | + ctx.Res.Json(new { Message = $"Profile with id {id} created." }); |
| 127 | + return ctx; |
| 128 | +}); |
| 129 | +``` |
| 130 | + |
| 131 | +### Retrieve a profile with GET |
| 132 | + |
| 133 | +```csharp title:Main/Main.cs |
| 134 | +api.Get("/profiles/:id", ctx => |
| 135 | +{ |
| 136 | + var id = ctx.Req.PathParams["id"]; |
| 137 | + try |
| 138 | + { |
| 139 | + var profile = profiles.Get(id); |
| 140 | + ctx.Res.Json(profile); |
| 141 | + } |
| 142 | + catch (Nitric.Sdk.Common.NotFoundException) |
| 143 | + { |
| 144 | + ctx.Res.Status = 404; |
| 145 | + ctx.Res.Json(new { Message = "Profile not found" }); |
| 146 | + } |
| 147 | + |
| 148 | + return ctx; |
| 149 | +}); |
| 150 | +``` |
| 151 | + |
| 152 | +### Remove a profile with DELETE |
| 153 | + |
| 154 | +```csharp title:Main/Main.cs |
| 155 | +api.Delete("/profiles/:id", ctx => |
| 156 | +{ |
| 157 | + var id = ctx.Req.PathParams["id"]; |
| 158 | + try |
| 159 | + { |
| 160 | + profiles.Delete(id); |
| 161 | + ctx.Res.Json(new { Message = $"Profile with id {id} deleted." }); |
| 162 | + } |
| 163 | + catch (Nitric.Sdk.Common.NotFoundException) |
| 164 | + { |
| 165 | + ctx.Res.Status = 404; |
| 166 | + ctx.Res.Json(new { Message = "Profile not found" }); |
| 167 | + } |
| 168 | + return ctx; |
| 169 | +}); |
| 170 | +``` |
| 171 | + |
| 172 | +### List all profiles with GET |
| 173 | + |
| 174 | +```csharp title:Main/Main.cs |
| 175 | +api.Get("/profiles", ctx => |
| 176 | +{ |
| 177 | + var keys = profiles.Keys(); |
| 178 | + var profilesList = new List<string>(); |
| 179 | + |
| 180 | + while (keys.MoveNext().GetAwaiter().GetResult()) |
| 181 | + { |
| 182 | + profilesList.Add(keys.Current); |
| 183 | + } |
| 184 | + ctx.Res.Json(profilesList); |
| 185 | + return ctx; |
| 186 | +}); |
| 187 | +``` |
| 188 | + |
| 189 | +## Ok, let's run this thing! |
| 190 | + |
| 191 | +Now that you have an API defined with handlers for each of its methods, it's time to test it locally. |
| 192 | + |
| 193 | +```bash |
| 194 | +nitric start |
| 195 | +``` |
| 196 | + |
| 197 | +## Test the API |
| 198 | + |
| 199 | +Below are some example requests you can use to test the API. You'll need to update all values in brackets `[]` and change the URL to your deployed URL if you're testing on the cloud. |
| 200 | + |
| 201 | +### Create Profile |
| 202 | + |
| 203 | +```bash |
| 204 | +curl --location --request POST 'http://localhost:4001/profiles' \ |
| 205 | +--header 'Content-Type: text/plain' \ |
| 206 | +--data-raw '{ |
| 207 | + "name": "Peter Parker", |
| 208 | + "age": "21", |
| 209 | + "homeTown" : "Queens" |
| 210 | +}' |
| 211 | +``` |
| 212 | + |
| 213 | +### Fetch Profile |
| 214 | + |
| 215 | +```bash |
| 216 | +curl --location --request GET 'http://localhost:4001/profiles/[id]' |
| 217 | +``` |
| 218 | + |
| 219 | +### Fetch All Profiles |
| 220 | + |
| 221 | +```bash |
| 222 | +curl --location --request GET 'http://localhost:4001/profiles' |
| 223 | +``` |
| 224 | + |
| 225 | +### Delete Profile |
| 226 | + |
| 227 | +```bash |
| 228 | +curl --location --request DELETE 'http://localhost:4001/profiles/[id]' |
| 229 | +``` |
| 230 | + |
| 231 | +## Deploy to the cloud |
| 232 | + |
| 233 | +At this point, you can deploy the application to any supported cloud provider. Start by setting up your credentials and any configuration for the cloud you prefer: |
| 234 | + |
| 235 | +- [AWS](/providers/pulumi/aws) |
| 236 | +- [Azure](/providers/pulumi/azure) |
| 237 | +- [Google Cloud](/providers/pulumi/gcp) |
| 238 | + |
| 239 | +Next, we'll need to create a `stack`. Stacks represent deployed instances of an application, including the target provider and other details such as the deployment region. You'll usually define separate stacks for each environment such as development, testing and production. |
| 240 | + |
| 241 | +```bash |
| 242 | +nitric stack new dev aws |
| 243 | +``` |
| 244 | + |
| 245 | +Continue by checking your stack file `nitric.dev.yaml` and adding in your preferred region, let's use `us-east-1`. |
| 246 | + |
| 247 | +```yaml title:nitric.dev.yaml |
| 248 | +# The nitric provider to use |
| 249 | +provider: nitric/aws@latest |
| 250 | +# The target aws region to deploy to |
| 251 | +# See available regions: |
| 252 | +# https://docs.aws.amazon.com/general/latest/gr/lambda-service.html |
| 253 | +region: us-east-1 |
| 254 | +``` |
| 255 | +
|
| 256 | +<Note> |
| 257 | + Cloud deployments incur costs and while most of these resource are available |
| 258 | + with free tier pricing you should consider the costs of the deployment. |
| 259 | +</Note> |
| 260 | +
|
| 261 | +We can deploy our application using the following command: |
| 262 | +
|
| 263 | +```bash |
| 264 | +nitric up |
| 265 | +``` |
| 266 | + |
| 267 | +When the deployment is complete, go to the relevant cloud console and you'll be able to see and interact with your API. If you'd like to make changes to the API you can apply those changes by re-running the `up` command. Nitric will automatically detect what's changed and just update the relevant cloud resources. |
| 268 | + |
| 269 | +When you're done testing your application you can tear it down from the cloud, use the `down` command: |
| 270 | + |
| 271 | +```bash |
| 272 | +nitric down |
| 273 | +``` |
| 274 | + |
| 275 | +## Optional - Add profile image upload/download support |
| 276 | + |
| 277 | +### Access profile buckets with permissions |
| 278 | + |
| 279 | +```csharp title:Main/Main.cs |
| 280 | +var images = Application.Bucket("images").Allow(BucketPermission.Read, BucketPermission.Write); |
| 281 | +``` |
| 282 | + |
| 283 | +### Get a URL to upload a profile image |
| 284 | + |
| 285 | +```csharp title:Main/Main.cs |
| 286 | +api.Get("/profiles/:id/image/upload", ctx => |
| 287 | +{ |
| 288 | + var id = ctx.Req.Params["id"]; |
| 289 | + var url = images.File($"images/{id}/photo.png").GetUploadUrl(); |
| 290 | + ctx.Res.Json(new { Url = url }); |
| 291 | + return ctx; |
| 292 | +}); |
| 293 | +``` |
| 294 | + |
| 295 | +### Get a URL to download a profile image |
| 296 | + |
| 297 | +```csharp title:Main/Main.cs |
| 298 | +api.Get("/profiles/:id/image/download", ctx => |
| 299 | +{ |
| 300 | + var id = ctx.Req.Params["id"]; |
| 301 | + var url = images.File($"images/{id}/photo.png").GetDownloadUrl(); |
| 302 | + ctx.Res.Json(new { Url = url }); |
| 303 | + return ctx; |
| 304 | +}); |
| 305 | +``` |
| 306 | + |
| 307 | +## Wrapping up |
| 308 | + |
| 309 | +With this guide, you've built a simple C# REST API with Nitric, tested it locally, and deployed it to the cloud. You can now expand on this API with additional functionality or integrate it into a larger system! |
0 commit comments