Certainly! CRUD operations refer to the basic actions that can be performed on a database or an API resource. CRUD stands for Create, Read, Update, and Delete. Here’s a summary of what each operation does in the context of an API project:
- Create ©:
Purpose: Used to create a new resource in the database.
HTTP Method: Typically implemented using the HTTP
POST
method.Example: Creating a new user by sending user data (such as name, email, etc.) to the server.
2. Read (R):
Purpose: Used to retrieve existing resources from the database.
HTTP Method: Implemented using the HTTP
GET
method.Example: Fetching a list of users or retrieving specific user details from the server.
3. Update (U):
Purpose: Used to modify or update existing resources in the database.
HTTP Method: Implemented using the HTTP
PUT
orPATCH
methods.Example: Modifying the details of an existing user, such as updating their email address or changing their password.
4. Delete (D):
- Purpose: Used to remove existing resources from the database.
HTTP Method: Implemented using the HTTP
DELETE
method.Example: Deleting a user account or removing a specific item from a collection.
In an API project, these CRUD operations are typically mapped to specific endpoints (URLs) and combined to perform various tasks related to managing data.
Below are some advantages for implementing this CRUD model:
- Simplicity and Consistency: CRUD operations provide a standardized way of interacting with data. Developers can follow the same patterns across different resources, making the API design intuitive and consistent.
2. Ease of Development: CRUD operations simplify the development process. Developers don’t have to reinvent the wheel for basic data management tasks. This speeds up the development cycle and reduces the time needed to create API endpoints.
3. Reusability: CRUD operations are reusable components. Once implemented, they can be used across different parts of the application, reducing redundant code and ensuring a more maintainable codebase.
4. Interoperability: CRUD operations are compatible with various client applications and frameworks. Since they adhere to standard HTTP methods, APIs built using CRUD operations can be consumed by a wide range of clients, including web browsers, mobile apps, and other services.
5. Flexibility: Developers have the flexibility to combine CRUD operations to create more complex workflows. For example, data retrieved using “Read” operations can be processed and modified before being updated or deleted, allowing for sophisticated data manipulation.
6. Scalability: CRUD operations can be optimized for performance, allowing the API to handle a large number of requests efficiently. Proper database indexing and caching techniques can be applied to scale CRUD operations to meet growing demands.
7. Simplified Testing: CRUD operations are straightforward to test. Each operation can be individually tested to ensure that it performs the intended action on the data. Automated tests can be easily written to validate the functionality of each CRUD operation.
8. Security: Developers can implement security measures at the level of CRUD operations. For example, authentication and authorization mechanisms can be applied to restrict certain operations to authorized users only, ensuring data security and integrity.
Now, we implement the CRUD operation in the .Net core API with Mongo DB as a Database with a Repository pattern.
Please read this blog to understand the repository pattern; I have previously discussed it extensively.
.Net core API project folder structure :
Catalog API project — contains information about the various products under different categories. For Example, The category named “Mobiles” contains different mobile phone brands as products.
Nuget package needed for Mongo DB: MongoDB.Driver
Under the Data folder set the Context class, this class is the gateway to connect for database.
// CatalogContext.cs
using System;
using Catalog.API.Entities;
using MongoDB.Driver;
namespace Catalog.API.Data
{
public class CatalogContext : ICatalogContext
{
public CatalogContext(IConfiguration configuration)
{
var client = new MongoClient(configuration.GetValue<string>("DatabaseSettings:ConnectionString"));
var database = client.GetDatabase(configuration.GetValue<string>("DatabaseSettings:DatabaseName"));
Products = database.GetCollection<Product>(configuration.GetValue<string>("DatabaseSettings:CollectionName"));
CatalogContextSeed.SeedData(Products);
}
public IMongoCollection<Product> Products { get; set; }
}
}
//ContextSeed class : insert the initial collection to DB
using System;
using Catalog.API.Entities;
using MongoDB.Driver;
namespace Catalog.API.Data
{
public class CatalogContextSeed
{
public static void SeedData(IMongoCollection<Product> productCollection)
{
bool existProduct = productCollection.Find(p => true).Any();
if (!existProduct)
{
productCollection.InsertManyAsync(GetPreconfiguredProducts());
}
}
private static IEnumerable<Product> GetPreconfiguredProducts()
{
return new List<Product>()
{
new Product()
{
Id = "602d2149e773f2a3990b47f5",
Name = "IPhone X",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "steve jobs iphone, iphone 15 currently now",
ImageFile = "product-1.png",
Price = 750.00M,
Category = "Smart Phone"
},
new Product()
{
Id = "602d2149e773f2a3990b47f6",
Name = "Samsung",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "samsung tough display",
ImageFile = "product-2.png",
Price = 840.00M,
Category = "Smart Phone"
},
new Product()
{
Id = "602d2149e773f2a3990b47f7",
Name = "Huawei Plus",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-3.png",
Price = 650.00M,
Category = "White Appliances"
},
new Product()
{
Id = "602d2149e773f2a3990b47f8",
Name = "Xiaomi Mi 9",
Summary = "This phone is the company's biggest change to its flagship smartphone in years. It includes a borderless.",
Description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut, tenetur natus doloremque laborum quos iste ipsum rerum obcaecati impedit odit illo dolorum ab tempora nihil dicta earum fugiat. Temporibus, voluptatibus.",
ImageFile = "product-4.png",
Price = 470.00M,
Category = "White Appliances"
}
};
}
}
}
//interface iContext
using System;
using Catalog.API.Entities;
using MongoDB.Driver;
namespace Catalog.API.Data
{
public interface ICatalogContext
{
IMongoCollection<Product> Products { get; set; }
}
}
Entities Model class for Products:
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
namespace Catalog.API.Entities
{
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("Name")]
public string Name { get; set; }
public string Category { get; set; }
public string Summary { get; set; }
public string Description { get; set; }
public string ImageFile { get; set; }
public decimal Price { get; set; }
}
}
Under the Repositories folder, an interface is created in order to implement the CRUD methods that perform the DB operations.
using System;
using Catalog.API.Entities;
namespace Catalog.API.Repositories
{
public interface IProductRepository
{
Task<IEnumerable<Product>> GetProducts();
Task<Product> GetProduct(string id);
Task<IEnumerable<Product>> GetProductByName(string Name);
Task<IEnumerable<Product>> GetProductByCategory(string CategoryName);
Task CreateProduct(Product product);
Task<bool> UpdateProduct(Product product);
Task<bool> DeleteProduct(string id);
}
}
// ProductRepository class
using System;
using System.Xml.Linq;
using Catalog.API.Data;
using Catalog.API.Entities;
using MongoDB.Driver;
namespace Catalog.API.Repositories
{
public class ProductRepository : IProductRepository
{
private readonly ICatalogContext _context;
public ProductRepository(ICatalogContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task CreateProduct(Product product)
{
await _context.Products.InsertOneAsync(product);
}
public async Task<bool> DeleteProduct(string id)
{
FilterDefinition<Product> filter = Builders<Product>.Filter.Eq(p => p.Id, id);
var deleteResult = await _context.Products.DeleteOneAsync(filter);
return deleteResult.IsAcknowledged && deleteResult.DeletedCount > 0;
}
public async Task<Product> GetProduct(string id)
{
return await _context.Products.Find(p => p.Id == id).FirstOrDefaultAsync();
}
public async Task<IEnumerable<Product>> GetProductByCategory(string categoryName)
{
FilterDefinition<Product> filter = Builders<Product>.Filter.Eq(p => p.Category, categoryName);
return await _context.Products.Find(filter).ToListAsync();
}
public async Task<IEnumerable<Product>> GetProductByName(string Name)
{
FilterDefinition<Product> filter = Builders<Product>.Filter.Eq(p => p.Name, Name);
return await _context.Products.Find(filter).ToListAsync();
}
public async Task<IEnumerable<Product>> GetProducts()
{
return await _context.Products.Find(p => true).ToListAsync();
}
public async Task<bool> UpdateProduct(Product product)
{
var updateResult = await _context.Products.ReplaceOneAsync(filter: g => g.Id == product.Id, replacement: product);
return updateResult.IsAcknowledged && updateResult.ModifiedCount > 0;
}
}
}
Once the Context and Repo classes have been completed, this is called from the Controller using the actual Urls. ProductRepository is injected into this controller to call the following methods:
using Catalog.API.Entities;
using Catalog.API.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace Catalog.API.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
public class CatalogController : ControllerBase
{
private readonly IProductRepository _repository;
private readonly ILogger<CatalogController> _logger;
public CatalogController(IProductRepository repository, ILogger<CatalogController> logger)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Product>), (int)HttpStatusCode.OK)]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var products = await _repository.GetProducts();
return Ok(products);
}
[HttpGet("{id:length(24)}", Name = "GetProduct")]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task<ActionResult<Product>> GetProductById(string id)
{
var product = await _repository.GetProduct(id);
if (product == null)
{
_logger.LogError($"Product with id: {id}, not found.");
return NotFound();
}
return Ok(product);
}
[Route("[action]/{category}", Name = "GetProductByCategory")]
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Product>), (int)HttpStatusCode.OK)]
public async Task<ActionResult<IEnumerable<Product>>> GetProductByCategory(string category)
{
var products = await _repository.GetProductByCategory(category);
return Ok(products);
}
[HttpPost]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task<ActionResult<Product>> CreateProduct([FromBody] Product product)
{
await _repository.CreateProduct(product);
return CreatedAtRoute("GetProduct", new { id = product.Id }, product);
}
[HttpPut]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task<IActionResult> UpdateProduct([FromBody] Product product)
{
return Ok(await _repository.UpdateProduct(product));
}
[HttpDelete("{id:length(24)}", Name = "DeleteProduct")]
[ProducesResponseType(typeof(Product), (int)HttpStatusCode.OK)]
public async Task<IActionResult> DeleteProductById(string id)
{
return Ok(await _repository.DeleteProduct(id));
}
}
}
Finally, make sure Dependency Injection is configured in the program class for the Context and Product classes.
builder.Services.AddScoped<ICatalogContext, CatalogContext>();
builder.Services.AddScoped<IProductRepository, ProductRepository>();
Once the above steps are completed , the APIs on Swagger will look like this:
The Studo3T client is linked to Mongo DB, and we can see the Mongo DB collections.
I’m attempting to turn this project into a microservice with Docker implementation; I’ll be writing a blog shortly about how microservices are handled with Docker implementation in it; keep tuned for my latest blog entries.