Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 8062c40

Browse files
committed
Add csharp rest api guide
1 parent 66417d4 commit 8062c40

File tree

5 files changed

+364
-8
lines changed

5 files changed

+364
-8
lines changed
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
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+
This example uses GUIDs to create unique IDs to store profiles against. 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!

src/components/code/CodeSwitcher.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ export async function CodeSwitcher({
2323
code.map((codeblock) => highlight(codeblock)),
2424
)
2525

26-
const missingLangs = languages.filter(
27-
(lang) => !highlighted.some((h) => h.lang === lang),
28-
)
26+
// TODO: Put this back when csharp references are updated
27+
// const missingLangs = languages.filter(
28+
// (lang) => !highlighted.some((h) => h.lang === lang),
29+
// )
2930

30-
if (missingLangs.length) {
31-
throw Error(
32-
`CodeSwitcher missing languages: ${missingLangs.join(', ')} at: ${highlighted[0].meta || 'unknown'}`,
33-
)
34-
}
31+
// if (missingLangs.length) {
32+
// throw Error(
33+
// `CodeSwitcher missing languages: ${missingLangs.join(', ')} at: ${highlighted[0].meta || 'unknown'}`,
34+
// )
35+
// }
3536

3637
if (tabs) {
3738
const children = highlighted.map((h) => (
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const DotNetLogoColour = (props: React.ComponentPropsWithoutRef<'svg'>) => {
2+
return (
3+
<svg
4+
width="32"
5+
height="32"
6+
viewBox="0 0 256 288"
7+
version="1.1"
8+
xmlns="http://www.w3.org/2000/svg"
9+
preserveAspectRatio="xMidYMid"
10+
>
11+
<g>
12+
<path
13+
d="M255.569,84.452376 C255.567,79.622376 254.534,75.354376 252.445,71.691376 C250.393,68.089376 247.32,65.070376 243.198,62.683376 C209.173,43.064376 175.115,23.505376 141.101,3.86637605 C131.931,-1.42762395 123.04,-1.23462395 113.938,4.13537605 C100.395,12.122376 32.59,50.969376 12.385,62.672376 C4.064,67.489376 0.015,74.861376 0.013,84.443376 C0,123.898376 0.013,163.352376 0,202.808376 C0,207.532376 0.991,211.717376 2.988,215.325376 C5.041,219.036376 8.157,222.138376 12.374,224.579376 C32.58,236.282376 100.394,275.126376 113.934,283.115376 C123.04,288.488376 131.931,288.680376 141.104,283.384376 C175.119,263.744376 209.179,244.186376 243.209,224.567376 C247.426,222.127376 250.542,219.023376 252.595,215.315376 C254.589,211.707376 255.582,207.522376 255.582,202.797376 C255.582,202.797376 255.582,123.908376 255.569,84.452376"
14+
fill="#A179DC"
15+
fill-rule="nonzero"
16+
></path>
17+
<path
18+
d="M128.182,143.241376 L2.988,215.325376 C5.041,219.036376 8.157,222.138376 12.374,224.579376 C32.58,236.282376 100.394,275.126376 113.934,283.115376 C123.04,288.488376 131.931,288.680376 141.104,283.384376 C175.119,263.744376 209.179,244.186376 243.209,224.567376 C247.426,222.127376 250.542,219.023376 252.595,215.315376 L128.182,143.241376"
19+
fill="#280068"
20+
fill-rule="nonzero"
21+
></path>
22+
<path
23+
d="M255.569,84.452376 C255.567,79.622376 254.534,75.354376 252.445,71.691376 L128.182,143.241376 L252.595,215.315376 C254.589,211.707376 255.58,207.522376 255.582,202.797376 C255.582,202.797376 255.582,123.908376 255.569,84.452376"
24+
fill="#390091"
25+
fill-rule="nonzero"
26+
></path>
27+
<path
28+
d="M201.892326,116.294008 L201.892326,129.767692 L215.36601,129.767692 L215.36601,116.294008 L222.102852,116.294008 L222.102852,129.767692 L235.576537,129.767692 L235.576537,136.504534 L222.102852,136.504534 L222.102852,149.978218 L235.576537,149.978218 L235.576537,156.71506 L222.102852,156.71506 L222.102852,170.188744 L215.36601,170.188744 L215.36601,156.71506 L201.892326,156.71506 L201.892326,170.188744 L195.155484,170.188744 L195.155484,156.71506 L181.6818,156.71506 L181.6818,149.978218 L195.155484,149.978218 L195.155484,136.504534 L181.6818,136.504534 L181.6818,129.767692 L195.155484,129.767692 L195.155484,116.294008 L201.892326,116.294008 Z M215.36601,136.504534 L201.892326,136.504534 L201.892326,149.978218 L215.36601,149.978218 L215.36601,136.504534 Z"
29+
fill="#FFFFFF"
30+
></path>
31+
<path
32+
d="M128.456752,48.625876 C163.600523,48.625876 194.283885,67.7121741 210.718562,96.0819435 L210.558192,95.808876 L169.209615,119.617159 C161.062959,105.823554 146.128136,96.5150717 128.996383,96.3233722 L128.456752,96.3203544 C102.331178,96.3203544 81.1506705,117.499743 81.1506705,143.625316 C81.1506705,152.168931 83.4284453,160.17752 87.3896469,167.094792 C95.543745,181.330045 110.872554,190.931398 128.456752,190.931398 C146.149522,190.931398 161.565636,181.208041 169.67832,166.820563 L169.481192,167.165876 L210.767678,191.083913 C194.51328,219.21347 164.25027,238.240861 129.514977,238.620102 L128.456752,238.625876 C93.2021701,238.625876 62.4315028,219.422052 46.0382398,190.902296 C38.0352471,176.979327 33.4561922,160.837907 33.4561922,143.625316 C33.4561922,91.1592636 75.9884604,48.625876 128.456752,48.625876 Z"
33+
fill="#FFFFFF"
34+
fill-rule="nonzero"
35+
></path>
36+
</g>
37+
</svg>
38+
)
39+
}
40+
41+
DotNetLogoColour.displayName = 'DotNetLogoColour'
42+
43+
export default DotNetLogoColour

src/components/icons/LanguageIcon.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import GoColorLogo from './GoLogoColour'
66
import DartLogoNoTextColour from './DartLogoNoTextColour'
77
import { cn } from '@/lib/utils'
88
import type { Language } from '@/lib/constants'
9+
import DotNetLogoColour from './DotNetColour'
910

1011
interface LanguageIconProps {
1112
name: Language
@@ -18,6 +19,7 @@ const icons: Record<Language, React.FC<{ className: string }>> = {
1819
python: PythonColorLogo,
1920
go: GoColorLogo,
2021
dart: DartLogoNoTextColour,
22+
csharp: DotNetLogoColour,
2123
}
2224

2325
export const LanguageIcon: React.FC<LanguageIconProps> = ({

0 commit comments

Comments
 (0)