In this tutorial, you will learn how to connect to a Couchbase Capella cluster to create, read, update, and delete documents and how to write simple parametrized SQL++ queries.
To run this prebuilt project, you will need:
When running Couchbase using Capella, the following prerequisites need to be met.
We will walk through the different steps required to get the application running.
git clone https://github.com/couchbase-examples/aspnet-quickstart.git
cd src/Org.Quickstart.API
dotnet restore
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.
To know more about connecting to your Capella cluster, please follow the instructions.
Specifically, you need to do the following:
All configuration for communication with the database is stored in the appsettings.Development.json file. This includes the connection string, username, password, bucket name and scope name. The default username is assumed to be Administrator
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.
"Couchbase": {
"BucketName": "travel-sample",
"ScopeName": "inventory",
"ConnectionString": "couchbases://yourassignedhostname.cloud.couchbase.com",
"Username": "Administrator",
"Password": "P@ssw0rd12",
"IgnoreRemoteCertificateNameMismatch": true,
"HttpIgnoreRemoteCertificateMismatch": true,
"KvIgnoreRemoteCertificateNameMismatch": true
}
Note: The connection string expects the
couchbases://
orcouchbase://
part.
At this point, we have installed the dependencies, loaded the travel-sample data and configured the application with the credentials. The application is now ready and you can run it.
cd src/Org.Quickstart.API
dotnet run
cd aspnet-quickstart
docker build -t couchbase-aspnet-quickstart .
cd aspnet-quickstart
docker run -e DB_CONN_STR=<connection_string> -e DB_USERNAME=<user_with_read_write_permission_to_travel-sample_bucket> -e DB_PASSWORD=<password_for_user> -p 8080:8080 couchbase-aspnet-quickstart
You can access the Application on http://localhost:8080/index.html
Once the application starts, you can see the details of the application on the logs.
The application will run on port 8080 of your local machine (http://localhost:8080/index.html). You will find the Swagger documentation of the API if you go to the URL in your browser. Swagger documentation is used in this demo to showcase the different API end points and how they can be invoked. More details on the Swagger documentation can be found in the appendix.
For this tutorial, we use three collections, airport
, airline
and route
that contain sample airports, airlines and airline routes respectively. The route collection connects the airports and airlines as seen in the figure below. We use these connections in the quickstart to generate airports that are directly connected and airlines connecting to a destination airport. Note that these are just examples to highlight how you can use SQL++ queries to join the collections.
To begin this tutorial, clone the repo and open it up in the IDE of your choice. Now you can learn about how to create, read, update and delete documents in Couchbase Server.
├── src
│ ├── Org.Quickstart.API
│ │ ├── Controllers
│ │ │ ├── AirlineController.cs
│ │ │ ├── AirportController.cs
│ │ │ └── RouteController.cs
│ │ ├── Models
│ │ │ ├── Airline.cs
│ │ │ ├── Airport.cs
│ │ │ └── Route.cs
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── Services
│ │ │ └── InventoryScopeService.cs
│ │ ├── Couchbase.TravelSample.csproj
│ │ ├── Program.cs
│ │ └── appsettings.Development.json
│ └── Org.Quickstart.IntegrationTests
│ ├── ControllerTests
│ │ ├── AirlineTests.cs
│ │ ├── AirportTests.cs
│ │ └── RouteTests.cs
│ └── Org.Quickstart.IntegrationTests.csproj
├── Org.Quickstart.sln
└── Dockerfile
Org.Quickstart.API.Program:
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.Development.json
file.
// Register the configuration for Couchbase and Dependency Injection Framework
builder.Services.Configure<CouchbaseConfig>(config);
builder.Services.AddCouchbase(config);
The services are added to the DI container. CORS policy is defined to allow specific origins. HttpClient, Controllers, Endpoints API Explorer, and Swagger are also added here. The Swagger setup includes a detailed description of the API.
builder.Services.AddCors(options =>
{
// ...
});
builder.Services.AddHttpClient();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
// ...
});
The InventoryScopeService is registered as a singleton service in the DI container.
builder.Services.AddSingleton<IInventoryScopeService, InventoryScopeService>();
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.
The program logs the Swagger UI address when the application starts.
var lifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStarted.Register(() =>
{
// ...
});
app.Lifetime.ApplicationStopped.Register(() =>
{
var cls = app.Services.GetRequiredService<ICouchbaseLifetimeService>();
cls.Close();
});
The program sets up the middleware pipeline for the application. This includes Swagger, CORS, HTTPS redirection, routing, authorization, and endpoint mapping.
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Couchbase Quickstart API v1");
c.RoutePrefix = string.Empty;
});
if (app.Environment.EnvironmentName == "Testing")
{
app.UseCors("_devAllowSpecificOrigins");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
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.
This InventoryScopeService
class is a service that provides a convenient way to fetch a specific scope from a specific bucket, handling all the necessary validation and error checking.
For this tutorial, we will focus on the airport entity. The other entities are similar.
We will be setting up a REST API to manage airport documents.
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 an ID (similar to a primary key in other databases) to save it to the database. This ID is passed in the URL. For other end points, we will use SQL++ to query for documents.
Our airport document will have an airportname, city, country, faa code, icao code, timezone info and the geographic coordinates. For this demo, we will store all airport information in one document in the airport
collection in the travel-sample
bucket.
{
"airportname": "Sample Airport",
"city": "Sample City",
"country": "United Kingdom",
"faa": "SAA",
"icao": "SAAA",
"tz": "Europe/Paris",
"geo": {
"lat": 48.864716,
"lon": 2.349014,
"alt": 92
}
}
Open the AirportController.cs
file and navigate to the Post
method.
It takes an id
and a request
as parameters, where id
is the unique identifier for the airport and request
contains the data for the new airport. The method extracts the airport
object from the request
and attempts to insert it into the collection using the InsertAsync
method of the Couchbase SDK. If the insertion is successful, it returns a Created
result. However, if a document with the same id already exists in the collection, the method catches a DocumentExistsException
and returns a Conflict
result. For any other exceptions, it logs the error message and returns an InternalServerError
result.
public async Task<IActionResult> Post(string id, AirportCreateRequestCommand request)
{
try
{
var collection = await _inventoryScope.CollectionAsync(CollectionName);
var airport = request.GetAirport();
await collection.InsertAsync(id, airport);
return Created($"/api/v1/airport/{id}", airport);
}
catch (DocumentExistsException)
{
return Conflict($"A document with the ID '{id}' already exists.");
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, $"Error: {ex.Message} {ex.StackTrace} {Request.GetDisplayUrl()}");
}
}
Open the AirportController.cs
file and navigate to the GetById
method.
This method retrieves an airport document from a collection using its id
. It uses the GetAsync
method of the Couchbase SDK to fetch the document. If the document exists, it returns the document; otherwise, it returns a NotFound
result. Any other exceptions are caught, logged, and an InternalServerError
result is returned.
public async Task<IActionResult> GetById(string id)
{
try
{
var collection = await _inventoryScope.CollectionAsync(CollectionName);
var result = await collection.GetAsync(id);
var resultAirport = result.ContentAs<Airport>();
if (resultAirport != null)
{
return Ok(resultAirport);
}
}
catch (DocumentNotFoundException)
{
return NotFound();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, $"Error: {ex.Message} {ex.StackTrace} {Request.GetDisplayUrl()}");
}
return NotFound();
}
Open the AirportController.cs
file and navigate to the Update
method.
This method is used to update an existing airport document in the collection. It uses the GetAsync
and ReplaceAsync
methods from the Couchbase SDK. If the document with the specified id
exists, it replaces the document with the new data from the request
. If the document does not exist, it returns a NotFound
result. Any other exceptions are caught, logged, and an InternalServerError
result is returned.
public async Task<IActionResult> Update(string id, AirportCreateRequestCommand request)
{
try
{
var collection = await _inventoryScope.CollectionAsync(CollectionName);
if (await collection.GetAsync(id) is { } result)
{
result.ContentAs<Airport>();
await collection.ReplaceAsync(id, request.GetAirport());
return Ok(request);
}
else
{
return NotFound();
}
}
catch (DocumentNotFoundException)
{
return NotFound();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, $"Error: {ex.Message} {ex.StackTrace} {Request.GetDisplayUrl()}");
}
return NotFound();
}
Open the AirportController.cs
file and navigate to the Delete
method.
This method used to delete an existing airport document from a Couchbase collection. It uses the GetAsync
and RemoveAsync
methods from the Couchbase SDK. If the document with the specified id
exists, it removes the document from the collection. If the document does not exist, it returns a NotFound
result. Any other exceptions are caught, logged, and an InternalServerError
result is returned.
public async Task<IActionResult> Delete(string id)
{
try
{
var collection = await _inventoryScope.CollectionAsync(CollectionName);
var result = await collection.GetAsync(id);
var resultAirport = result.ContentAs<Airport>();
if (resultAirport != null)
{
await collection.RemoveAsync(id);
return Ok(id);
}
else
{
return NotFound();
}
}
catch (DocumentNotFoundException)
{
return NotFound();
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
}
return NotFound();
}
This endpoint retrieves the list of airports in the database. The API has options to specify the page size for the results and country from which to fetch the airport documents.
SQL++ is a powerful query language based on SQL, but designed for structured and flexible JSON documents. We will use a SQL+ query to search for airports with Limit, Offset, and Country option.
Open the AirportController.cs
file and navigate to the Delete
method. This endpoint is different from the others we have seen before because it makes the SQL++ query rather than a key-value operation. This usually means more overhead because the query engine is involved. For this query, we are using the predefined indices in the travel-sample
bucket. We can create an additional index specific for this query to make it perform better.
We need to get the values from the query string for country, limit, and Offset that we will use in our query. These are pulled from the queryParameters.Parameter
method.
This end point has two queries depending on the value for the country parameter. If a country name is specified, we retrieve the airport documents for that specific country. If it is not specified, we retrieve the list of airports across all countries. The queries are slightly different for these two scenarios.
We build our SQL++ query using the parameters specified by $
symbol for both these scenarios. The difference between the two queries is the presence of the country
parameter in the query. Normally for the queries with pagination, it is advised to order the results to maintain the order of results across multiple queries.
Next, we pass that query
to the QueryAsync
method. We save the results in a list, items
.
This endpoint calls the QueryAsync
method defined in the Scope by the Couchbase SDK.
var queryParameters = new Couchbase.Query.QueryOptions();
queryParameters.Parameter("limit", limit ?? 10);
queryParameters.Parameter("offset", offset ?? 0);
string query;
if (!string.IsNullOrEmpty(country))
{
query = $@"SELECT airport.airportname,
airport.city,
airport.country,
airport.faa,
airport.geo,
airport.icao,
airport.tz
FROM airport AS airport
WHERE lower(airport.country) = $country
ORDER BY airport.airportname
LIMIT $limit
OFFSET $offset";
queryParameters.Parameter("country", country.ToLower());
}
else
{
query = $@"SELECT airport.airportname,
airport.city,
airport.country,
airport.faa,
airport.geo,
airport.icao,
airport.tz
FROM airport AS airport
ORDER BY airport.airportname
LIMIT $limit
OFFSET $offset";
}
var results = await _inventoryScope.QueryAsync<Airport>(query, queryParameters);
var items = await results.Rows.ToListAsync();
return items.Count == 0 ? NotFound() : Ok(items);
This endpoint fetches the airports that can be reached directly from the specified source airport code. This also uses a SQL++ query to fetch the results similar to the List Airport endpoint.
Let us look at the query used here:
SELECT distinct (route.destinationairport)
FROM airport as airport
JOIN route as route on route.sourceairport = airport.faa
WHERE airport.faa = $airport and route.stops = 0
ORDER BY route.destinationairport
LIMIT $limit
OFFSET $offset
We are fetching the direct connections by joining the airport collection with the route collection and filtering based on the source airport specified by the user and by routes with no stops.
We have defined integration tests using the xunit nuget package for all the API end points. The integration tests use the same database configuration as the application. For the integration tests, we perform the operation using the API and confirm the results by checking the documents in the database. For example, to check the creation of the document by the API, we would call the API to create the document and then read the same document from the database and compare them. After the tests, the documents are cleaned up by calling the DELETE endpoint
To run the standard integration tests, use the following commands:
cd ../Org.Quickstart.IntegrationTests/
dotnet restore
dotnet build
dotnet test
If you would like to add another entity to the APIs, these are the steps to follow:
Controllers
folder similar to the existing ones like AirportController.cs
.ControllerTests
folder similar to AirportTests.cs
.If you are running this quickstart with a self managed Couchbase cluster, you need to load the travel-sample data bucket in your cluster and generate the credentials for the bucket.
You need to update the connection string and the credentials in the appsettings.Development.json file in the source folder.
NOTE: Couchbase must be installed and running prior to running the the ASP.NET app.
Swagger documentation provides a clear view of the API including endpoints, HTTP methods, request parameters, and response objects.
Click on an individual endpoint to expand it and see detailed information. This includes the endpoint's description, possible response status codes, and the request parameters it accepts.
You can try out an API by clicking on the "Try it out" button next to the endpoints.
Parameters: If an endpoint requires parameters, Swagger UI provides input boxes for you to fill in. This could include path parameters, query strings, headers, or the body of a POST/PUT request.
Execution: Once you've inputted all the necessary parameters, you can click the "Execute" button to make a live API call. Swagger UI will send the request to the API and display the response directly in the documentation. This includes the response code, response headers, and response body.
Swagger documents the structure of request and response bodies using models. These models define the expected data structure using JSON schema and are extremely helpful in understanding what data to send and expect.