In this article, you will learn how to connect to a Couchbase cluster to create, read, update, and delete documents and how to write simple parametrized N1QL queries.
To run this prebuilt project, you will need:
git clone https://github.com/couchbase-examples/aspnet-quickstart-minapi
cd src/Couchbase.Quickstart
dotnet restore
Note: Nuget packages auto restore when building the project in Visual Studio Professional and Visual Studio for Mac
The Couchbase SDK for .NET includes a nuget package called Couchbase.Extensions.DependencyInjection
which is designed for environments like ASP.NET that takes in a configuration to connect to Couchbase and automatically registers interfaces that you can use in your code to perform full CRUD (create, read, update, delete)
operations and queries against the database.
All configuration for communication with the database is stored in the appsettings.Development.json file. This includes the connection string, username, password, bucket name, colleciton name, and scope name. The default username is assumed to be admin
and the default password is assumed to be P@$$w0rd12
. If these are different in your environment you will need to change them before running the application.
With this tutorial, it's required that a database user and bucket be created prior to running the application.
For Capella users, follow the directions found on the documentation website for creating a bucket called user_profile
. Next, follow the directions for Configure Database Credentials; name it admin
with a password of P@$$w0rd12
.
Next, open the appsettings.Development.json file. Locate the ConnectionString property and update it to match your Wide Area Network name found in the Capella Portal UI Connect tab. Note that Capella uses TLS so the connection string must start with couchbases://. Note that this configuration is designed for development environments only.
"Couchbase": {
"BucketName": "user_profile",
"ScopeName": "_default",
"CollectionName": "profile",
"ConnectionString": "couchbases://yourassignedhostname.cloud.couchbase.com",
"Username": "admin",
"Password": "P@$$w0rd12",
"IgnoreRemoteCertificateNameMismatch": true,
"HttpIgnoreRemoteCertificateMismatch": true,
"KvIgnoreRemoteCertificateNameMismatch": true
}
Couchbase Capella users that do not follow these directions will get exception errors and the Swagger portal will return errors when running the APIs.
For local installation and docker users, follow the directions found on the documentation website for creating a bucket called user_profile
. Next, follow the directions for Creating a user; name it admin
with a password of P@$$w0rd12
. For this tutorial, make sure it has Full Admin
rights so that the application can create collections and indexes.
Next, open the appsettings.Development.json file and validate the configuration information matches your setup.
NOTE: For docker and local Couchbase installations, Couchbase must be installed and running on localhost (http://127.0.0.1:8091) prior to running the the ASP.NET app.
At this point the application is ready and you can run it:
cd src/Couchbase.Quickstart
dotnet run
Once the site is up and running you can launch your browser and go to the Swagger start page to test the APIs.
A simple REST API using ASP.NET Minimum API and the Couchbase SDK version 3.x with the following endpoints:
We will be setting up a REST API to manage some profile documents. Our profile document will have an auto-generated GUID for its key, first and last name of the user, an email, and hashed password. For this demo we will store all profile information in just one document in a collection named profile
:
{
"pid": "b181551f-071a-4539-96a5-8a3fe8717faf",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@couchbase.com",
"password": "$2a$10$tZ23pbQ1sCX4BknkDIN6NekNo1p/Xo.Vfsttm.USwWYbLAAspeWsC"
}
As we can see, we want our user's password to be encrypted in the database too, we can achieve this simply with bcrypt, a dependency we have installed.
To begin clone the repo and open it up in the IDE of your choice to learn about how to create, read, update and delete documents in your Couchbase Server. Before we can get into the controller code, let's review the standard way you setup the Couchbase SDK in ASP.NET.
In order to use the Couchbase.Extensions.DependencyInjection
framework, we must first register the service. The Couchbase Services requires the database configuration information, which can be provided by reading the database configuration from the appsettings.json file.
//register the configuration for Couchbase and Dependency Injection Framework
builder.Services.Configure<CouchbaseConfig>(builder.Configuration.GetSection("Couchbase"));
builder.Services.AddCouchbase(builder.Configuration.GetSection("Couchbase"));
ASP.NET has an interface called IHostApplicationLifetime
that you can add to your Configure method to help with registration of lifetime events. The Couchbase SDK provides the ICouchbaseLifetimeService
interface for handling closing the database connections when the application closes.
It's best practice to register for the ASP.NET ApplicationStop
lifetime event and call the ICouchbaseLifetimeService
Close method so that the database connection and resources are closed and removed gracefully.
//remove couchbase from memory when ASP.NET closes
app.Lifetime.ApplicationStopped.Register(() =>
{
var cls = app.Services.GetRequiredService<ICouchbaseLifetimeService>();
if (cls != null)
{
cls.Close();
}
});
The Couchbase .NET SDK will handle all communications to the database cluster, so you shouldn't need to worry about creating a pool of connections.
The DatabaseService class is a convience class that has a method SetupDatabase which is called in the ASP.NET ApplicationStarted
lifetime event. This is used to to automatically create the collection and indexes used in this Quickstart.
if (app.Environment.EnvironmentName == "Testing")
{
app.UseCors(_devSpecificOriginsName);
//assume that bucket, collection, and indexes already exists due to latency in creating and async
}
else
{
//setup the database once everything is setup and running
app.Lifetime.ApplicationStarted.Register(async () =>
{
var db = app.Services.GetService<Couchbase.Quickstart.Services.DatabaseService>();
//**WARNING** - this code assumes the bucket has already been created
//if you don't create it you will get errors
if (db != null)
{
//create collection to store documents in
await db.CreateCollection();
//creates the indexes for our SQL++ query
await db.CreateIndex();
}
});
}
For CRUD operations we will use the Key Value operations that are built into the Couchbase SDK to create, read, update, and delete a document. Every document will need a ID (simlar to a primary key in other databases) in order to save it to the database.
Open the Program.cs file navigate to the app.MapPost method call. Let’s break this code down.
app.MapPost("/api/v1/profiles",
async (
ProfileCreateRequestCommand request,
IBucketProvider bucketProvider,
IOptions<CouchbaseConfig> options) => // <1>
{
//get couchbase config values from appsettings.json
var couchbaseConfig = options.Value; // <2>
//get the bucket, scope, and collection
var bucket = await bucketProvider.GetBucketAsync(couchbaseConfig.BucketName); // <3>
var scope = bucket.Scope(couchbaseConfig.ScopeName); // <4>
var collection = scope.Collection(couchbaseConfig.CollectionName); // <5>
//get profile from request
var profile = request.GetProfile(); // <6>
//set documentId
profile.Pid = Guid.NewGuid(); // <7>
//save documentg
await collection.InsertAsync(profile.Pid.ToString(), profile); // <8>
return Results.Created($"/api/v1/profile/{profile.Pid}", profile); // <9>
});
Pid
that we’re saving into the account object is a unique key. This is what we will use for our Key
to the document.profile
document is ready to be persisted to the database. We create an async call to the collection
using the InsertAsync
method.Navigate to the app.MapGet("/api/v1/profiles/{id}" method call the Program.cs file. We only need the profile ID or our Key
from the user to retrieve a particular profile document using a basic key-value operation which is passed in the method signature as a Guid. Since we created the document with a unique key we can use that key to find the document in the scope and collection it is stored in. We need access to the bucket so we can use dependency injection to pass in the IBucketProvider and IOptions to get the configuration from our appsettings.json file.
app.MapGet("/api/v1/profiles/{id}", async (Guid id, IBucketProvider bucketProvider, IOptions<CouchbaseConfig> options) =>
{
try
{
//get couchbase config values from appsettings.json
var couchbaseConfig = options.Value; // <1>
//get the bucket, scope, and collection
var bucket = await bucketProvider.GetBucketAsync(couchbaseConfig.BucketName); // <2>
var scope = bucket.Scope(couchbaseConfig.ScopeName); // <3>
var collection = scope.Collection(couchbaseConfig.CollectionName); // <4>
//get the docment from the bucket using the id
var result = await collection.GetAsync(id.ToString()); // <5>
//validate we have a document
var resultProfile = result.ContentAs<Profile>(); // <6>
if (resultProfile != null) // <6>
{
return Results.Ok(resultProfile); // <6>
}
}
catch (Couchbase.Core.Exceptions.KeyValue.DocumentNotFoundException)
{
Results.NotFound(); // <7>
}
catch (Exception ex)
{
return Results.Problem(ex.Message);
}
return Results.NotFound();
});
Navigate to the app.MapPut method call the Program.cs file. We first look up the existing document and make sure it exists, if it does not, return a 500 level error code and message: "Cannot update: document not found".
Then, the entire document gets replaced except for the document key and the pid
field. The ProfileUpdateRequestCommand has a helper method that returns a Profile from the request object.
Finally, we create an async call to the collection
using the ReplaceAsync
method and then return the document saved and the result just as we did in the previous endpoint.
//get couchbase config values from appsettings.json
var couchbaseConfig = options.Value;
//get the bucket, scope, and collection
var bucket = await bucketProvider.GetBucketAsync(couchbaseConfig.BucketName);
var collection = bucket.Collection(couchbaseConfig.CollectionName);
//get current profile from the database
var result = await collection.GetAsync(request.Pid.ToString());
if (result != null)
{
var profile = result.ContentAs<Profile>();
var updateResult = await collection.ReplaceAsync<Profile>(request.Pid.ToString(), request.GetProfile());
return Results.Ok(request);
}
else
{
return Results.NotFound();
}
Navigate to the app.MapDelete method call the Program.cs file. We only need the Key
or id from the user to delete a document using a basic key-value operation. Note that in this code we first check if the document exists; if it doesn't then we return Results.NotFound(). Otherwise we remove the document using the RemoveAsync method call.
//get couchbase config values from appsettings.json
var couchbaseConfig = options.Value;
//get the bucket and collection
var bucket = await bucketProvider.GetBucketAsync(couchbaseConfig.BucketName);
var collection = bucket.Collection(couchbaseConfig.CollectionName);
//get the docment from the bucket using the id
var result = await collection.GetAsync(id.ToString());
//validate we have a document
var resultProfile = result.ContentAs<Profile>();
if (resultProfile != null)
{
await collection.RemoveAsync(id.ToString());
return Results.Ok(id);
}
else
{
return Results.NotFound();
}
N1QL is a powerful query language based on SQL, but designed for structed and flexible JSON documents. We will use a N1QL query to search for profiles with Skip, Limit, and Search options.
Navigate to the app.MapGet("/api/v1/profiles" method call the Program.cs file. This endpoint is different from all of the others because it makes the N1QL query rather than a key-value operation. This means more overhead because the query engine is involved. We did create an index specific for this query, so it should be performant.
First, the method signature uses search
, limit
, and skip
values for supporting basic paging operations. Then, we build our N1QL query using the parameters we just built.
Finally, we pass that query
to the cluster.QueryAsync
method and return the result.
Take notice of the N1QL syntax and how it targets the bucket
.scope
.collection
.
//create query using parameters to advoid SQL Injection
var cluster = await clusterProvider.GetClusterAsync();
var query = $@"SELECT p.* FROM `{couchbaseConfig.BucketName}
`.`{couchbaseConfig.ScopeName}
`.`{couchbaseConfig.CollectionName}`
p WHERE lower(p.firstName)
LIKE '%' || $search || '%'
OR lower(p.lastName) LIKE '%' || $search || '%'
LIMIT $limit OFFSET $skip";
//setup parameters
var queryParameters = new Couchbase.Query.QueryOptions();
queryParameters.Parameter("search", search.ToLower());
queryParameters.Parameter("limit", limit == null ? 5 : limit);
queryParameters.Parameter("skip", skip == null ? 0 : skip);
var results = await cluster.QueryAsync<Profile>(query, queryParameters);
var items = await results.Rows.ToListAsync<Profile>();
if (items.Count() == 0)
return Results.NotFound();
return Results.Ok(items);
To run the standard integration tests, use the following commands:
cd ../Couchbase.Quickstart.IntegrationTests/
dotnet restore
dotnet test
This project was based on the standard ASP.NET Template project and the default weather controller was removed.
Setting up a basic REST API in ASP.NET Minimum API with Couchbase is fairly simple, this project when run with Couchbase installed creates collection, an index for our parameterized N1QL query, and showcases basic CRUD operations needed in most applications.