Building a .NET Core REST API with MySQL CRUD with Image upload
Outline
-
- Introduction
- What We’ll Build
- Project Setup
- Installing Dependencies
- Creating the User Model
- Database Setup with MySQL
- Setting up DbContext
- Enabling Snake_Case JSON Serialization
- Building the UsersController
- Running Migrations
- Testing with Swagger or Postman
- Advanced Features (Optional)
- Password Hashing
- Image Upload
- Data Validation
- Full Source Code
- Conclusion & Next Steps
Introduction
In this comprehensive tutorial, we will build a powerful REST API using ASP.NET Core and MySQL. Our API will handle common user fields such as first_name
, last_name
, email
, password
, and image
. We’ll make sure the API works smoothly with Laravel or JavaScript frontends expecting snake_case JSON.
What We’ll Build
By the end of this guide, you’ll have a working .NET Core API that supports:
- CRUD operations on users
- JSON input/output in snake_case
- Connection to a MySQL database
- Modular architecture with controller, model, and DbContext
Project Structure
Here’s how your .NET Core Web API project should be organized. This structure ensures separation of concerns and makes your application easy to manage and scale.
UserApi/
├── Controllers/
│ └── UsersController.cs
├── Data/
│ └── AppDbContext.cs
├── Models/
│ └── User.cs
├── Migrations/
│ └── (auto-generated EF files after running migrations)
├── wwwroot/
│ └── images/ # Uploaded image files
├── JsonSnakeCaseNamingPolicy.cs
├── Program.cs
├── appsettings.json
├── UserApi.csproj
Notes:
Controllers
– Holds your API endpointsModels
– Contains your data model classesData
– Handles database configuration viaDbContext
Migrations
– Stores EF Core migration historywwwroot
– Serves static files like images
Project Setup
Open your terminal and run the following command to scaffold a new .NET Web API project:
dotnet new webapi -n UserApi
cd UserApi
This creates a folder UserApi
and sets up a boilerplate REST API. Open this folder in your IDE (e.g., VS Code or Visual Studio).
Installing Dependencies
To use MySQL with Entity Framework Core, you need to install the appropriate NuGet packages. Run the following commands inside your project directory:
dotnet add package Pomelo.EntityFrameworkCore.MySql
dotnet add package Microsoft.EntityFrameworkCore.Design
Here’s what these packages do:
Pomelo.EntityFrameworkCore.MySql
– MySQL database provider for Entity Framework CoreMicrosoft.EntityFrameworkCore.Design
– Required for running migrations and scaffolding
After running these commands, your project is ready to connect to a MySQL database.
Database Setup with MySQL
Before integrating with your database, ensure MySQL is installed and running. You can use tools like XAMPP, Laragon, or install MySQL directly on your machine.
Open a MySQL client (like phpMyAdmin or MySQL Workbench), and create a new database:
CREATE DATABASE user_api_db;
This database will store all user records. Be sure to note your MySQL username and password — you’ll need them for the connection string in the next step.
Setting up DbContext
In Entity Framework Core, the DbContext
class is used to interact with the database. Let’s create one that links to the User
model and connects to MySQL.
Inside your project, create a new folder named Data
. Then add a new file named AppDbContext.cs
with the following content:
using Microsoft.EntityFrameworkCore;
using UserApi.Models;
namespace UserApi.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
}
Next, open Program.cs
and configure the context with your MySQL connection string:
using UserApi.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(builder.Configuration.GetConnectionString(\"DefaultConnection\"),
new MySqlServerVersion(new Version(8, 0, 32))));
var app = builder.Build();
app.MapControllers();
app.Run();
Finally, in appsettings.json
, add the MySQL connection string:
{
\"ConnectionStrings\": {
\"DefaultConnection\": \"server=localhost;database=user_api_db;user=root;password=your_password\"
}
}
Replace your_password
with your actual MySQL root password. Now your application is connected to MySQL through Entity Framework Core.
Enabling Snake_Case JSON Serialization
By default, .NET Core serializes JSON in camelCase. However, to make your API compatible with Laravel or other systems using snake_case
, you’ll need to configure the JSON serializer.
Open your Program.cs
file and modify the controller services configuration like this:
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy();
});
You now need to define the custom naming policy JsonSnakeCaseNamingPolicy
. Create a new file named JsonSnakeCaseNamingPolicy.cs
and add the following code:
using System.Text.Json;
public class JsonSnakeCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
return string.Concat(
name.Select((ch, i) =>
i > 0 && char.IsUpper(ch)
? \"_\" + ch.ToString().ToLower()
: ch.ToString().ToLower()
)
);
}
}
This class tells .NET to transform property names like FirstName
into first_name
during JSON serialization and deserialization.
Building the UsersController
Now that everything is connected, let’s build the API controller for the /ap/users
endpoint. Create a new folder called Controllers
and add a file named UsersController.cs
:
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using UserApi.Data;
using UserApi.Models;
namespace UserApi.Controllers
{
[ApiController]
[Route("ap/users")]
public class UsersController : ControllerBase
{
private readonly AppDbContext _context;
public UsersController(AppDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult> GetUsers()
{
var users = await _context.Users.ToListAsync();
return Ok(users);
}
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null) return NotFound();
return Ok(user);
}
[HttpPost]
public async Task<ActionResult> CreateUser(User user)
{
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateUser(int id, User updatedUser)
{
if (id != updatedUser.Id) return BadRequest();
_context.Entry(updatedUser).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null) return NotFound();
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return NoContent();
}
}
}
This controller includes all CRUD endpoints: list all users, get user by ID, create, update, and delete a user. All routes are based at /ap/users
, just like you requested.
Running Migrations
Now that you’ve set up the model, DbContext, and controller, it’s time to generate and apply a migration so Entity Framework can create the MySQL database tables.
Open your terminal and run the following command to scaffold the initial migration:
dotnet ef migrations add InitialCreate
This creates a new folder called Migrations
with files that describe how to create your database schema based on the User
model.
Next, apply the migration to create the database structure in MySQL:
dotnet ef database update
If everything is set up correctly, you’ll now see the Users
table inside your user_api_db
database. You’re now ready to test the API.
Testing with Swagger or Postman
After building your controller and running the migration, it’s time to test your API endpoints.
Option 1: Using Swagger UI
By default, .NET Core includes Swagger support in development mode. Run your project:
dotnet run
Then visit https://localhost:5001/swagger
in your browser. You’ll see a graphical interface where you can:
- List all users
- Create a user
- Update a user
- Delete a user
Option 2: Using Postman
You can also test the API with Postman. Here are the typical endpoints:
GET /ap/users
– List all usersGET /ap/users/{id}
– Get a specific userPOST /ap/users
– Create a new userPUT /ap/users/{id}
– Update a userDELETE /ap/users/{id}
– Delete a user
Don’t forget to set the Content-Type
to application/json
in Postman when sending data.
Advanced Features (Optional)
Password Hashing
Storing plain-text passwords is insecure. Instead, hash them before saving. You can use BCrypt.Net
or ASP.NET Core Identity
.
Install the package:
dotnet add package BCrypt.Net-Next
Modify the POST
method in UsersController
:
user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password);
Image Upload
To allow file uploads (e.g., profile images), change your POST
action to accept multipart form data.
Update the model or store the image file path after saving it to a directory:
[HttpPost("upload")]
public async Task<IActionResult> UploadImage(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded.");
var path = Path.Combine("wwwroot/images", file.FileName);
using (var stream = new FileStream(path, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Ok(new { image = $"/images/{file.FileName}" });
}
Data Validation
Make use of data annotations like [Required]
, [MaxLength]
, and [EmailAddress]
in your model to ensure proper validation.
You can also validate manually in your controller:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Full Source Code
Here’s a summary of all essential files and code used in this tutorial. You can copy/paste or organize these into your own project structure.
Models/User.cs
public class User
{
public int Id { get; set; }
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? Image { get; set; }
}
Data/AppDbContext.cs
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}
Controllers/UsersController.cs
[ApiController]
[Route("ap/users")]
public class UsersController : ControllerBase
{
private readonly AppDbContext _context;
public UsersController(AppDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult> GetUsers() => Ok(await _context.Users.ToListAsync());
[HttpGet("{id}")]
public async Task<ActionResult> GetUser(int id)
{
var user = await _context.Users.FindAsync(id);
return user == null ? NotFound() : Ok(user);
}
[HttpPost]
public async Task<ActionResult> CreateUser(User user)
{
user.Password = BCrypt.Net.BCrypt.HashPassword(user.Password);
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateUser(int id, User user)
{
if (id != user.Id) return BadRequest();
_context.Entry(user).State = EntityState.Modified;
await _context.SaveChangesAsync();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteUser(int id)
{
var user = await _context.Users.FindAsync(id);
if (user == null) return NotFound();
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return NoContent();
}
}
Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy();
});
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(
builder.Configuration.GetConnectionString("DefaultConnection"),
new MySqlServerVersion(new Version(8, 0, 32))
));
var app = builder.Build();
app.MapControllers();
app.Run();
JsonSnakeCaseNamingPolicy.cs
public class JsonSnakeCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name)
{
return string.Concat(
name.Select((ch, i) =>
i > 0 && char.IsUpper(ch)
? \"_\" + ch.ToString().ToLower()
: ch.ToString().ToLower()
)
);
}
}
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "server=localhost;database=user_api_db;user=root;password=your_password"
}
}
Conclusion & Next Steps
Congratulations! 🎉 You’ve successfully built a full-featured REST API using ASP.NET Core with a MySQL backend and Laravel-compatible snake_case
JSON responses.
This API supports all CRUD operations, proper model validation, secure password handling, and even image uploads. You now have a solid foundation for building more advanced applications or integrating with frontend frameworks like Vue, React, or even mobile apps.
Next Steps
- ✅ Add JWT authentication for user login and protected routes
- ✅ Implement pagination, search, or filters for the users list
- ✅ Create a Laravel frontend or Vue SPA to consume this API
- ✅ Deploy your API to a cloud platform (like Azure, Heroku, or DigitalOcean)
Thank you for following this tutorial. If you found it helpful, consider sharing it or bookmarking it for future reference. Happy coding! 💻🚀