Mert Özen Further With Every Line
Backend .NET Core Web API — #07

What Is a DTO and Why Shouldn't We Return the Entity Directly?

Mert Özen Aug 2, 2026 11 min 81 views
What Is a DTO and Why Shouldn't We Return the Entity Directly?

We cover what DTOs are and why you shouldn't return database objects directly: the concrete benefits of using DTOs for security, privacy, flexibility, and contract stability.

So far, in our examples we've returned either simple texts or name lists. But in a real application, what you'll return is usually a database record: a user, a product, an order. And right here there's a mistake most new developers make without realizing it: taking the object coming from the database and returning it to the outside as is. Today we talk about why we shouldn't do this and the DTO concept we'll use instead.

Let's Clarify the Terms First

Let's start by separating two words. An entity is the class that represents a table in the database; it contains all the fields of that record. A User entity may contain the user's name and email, but also fields the outside world has no need to see at all, like the hash of their password, the creation date, and internal record numbers.

A DTO, on the other hand, means "Data Transfer Object." As the name suggests: its only job is to carry data between two points. A DTO is a plain carrier class that contains exactly what you want to show to the outside, and only that. You could say it's the trimmed, polished version of the entity, ready to be presented to the outside.

So Why Is Returning the Entity Directly Bad?

The code runs, the data goes; at first glance there seems to be no problem. But beneath this habit lie a few serious risks.

Leaking Hidden Information

This is the most dangerous one. If you return your entity as is, every field inside it becomes part of the response. Fields like the password hash, internal notes, and deletion status can leak to the outside without your awareness. One day you add a new sensitive field to the User entity, and that field starts appearing in API responses without you even knowing. When you use a DTO, only the fields you defined in the DTO enter the response; whatever else there is stays out.

The Contract Becoming Fragile

The data your API returns to the outside is actually a contract between you and those who use it. If you return your entity directly, every change you make to your database structure also changes this contract. Just because you added a field to the table, your API response changes, and the mobile app using it may break unexpectedly. A DTO separates these two worlds: inside, you change the database however you like, while the outside contract stays fixed unless you want otherwise.

Loss of Flexibility

Most of the time, what you'll return to the outside isn't an exact copy of the entity. Sometimes you want to combine information from two tables into a single response, sometimes format a field differently, and sometimes not show a few fields at all. When you're bound to the entity, you don't have this flexibility. With a DTO, you fully determine the shape of your response.

What Does It Look Like?

Let's say we have an entity like this. It also contains fields we'd never want to show to the outside:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public string PasswordHash { get; set; }
    public DateTime CreatedAt { get; set; }
    public bool IsDeleted { get; set; }
}

If we return this entity directly, fields like PasswordHash and IsDeleted also become part of the response, which we definitely don't want. Instead, we define a DTO that contains only the fields we want to show to the outside:

public class UserDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

As you can see, the DTO is much plainer. There are only three fields whose exposure to the outside is harmless; there's no password hash or internal status information here. Now we return this DTO instead of the entity in our controller:

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var user = _users.FirstOrDefault(u => u.Id == id);

    if (user is null)
    {
        return NotFound();
    }

    var dto = new UserDto
    {
        Id = user.Id,
        Name = user.Name,
        Email = user.Email
    };

    return Ok(dto);
}

Here we do a manual copy from the entity to the DTO. We pull the user from the database but return only the DTO to the outside. This way, sensitive fields never enter the API response. This manual copying is perfectly fine in small projects; in a later article we'll also see AutoMapper, which automates this conversion, but seeing how the work is done by hand first is very valuable.

DTOs Are Also Used for Incoming Data

A DTO is useful not only when returning to the outside but also when receiving data. While creating a new user, you don't want to take fields like Id or CreatedAt from the client; you determine those. So you define a separate input DTO that contains only the fields you expect from the client:

public class CreateUserDto
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
}

This DTO says "while creating a new user, I expect these three pieces of information from you." The system will generate the id, the server will set the creation date, and you'll hash and store the password. There's no need for the client to interfere in these internal matters. Using a separate DTO for input and output may seem like extra classes at first, but it makes the code both secure and understandable.

A Small Experiment

Add a sensitive field like PasswordHash to your User entity and first return the entity directly, without using any DTO. When you look at the response, notice that the sensitive field appears too. Then define a UserDto, put only the safe fields inside it, and change the controller to return this DTO. When you place the two responses side by side, you'll see at a glance what the DTO is for. This small experiment permanently answers the question "why bother writing a separate class?"

In the next article we'll move on to model validation: we'll cover how to check whether the data in an incoming DTO is valid, along with the Data Annotations and FluentValidation approaches. The input DTOs you defined today will be exactly the right place to write those validation rules on top of.