10 minlesson

Address API Overview

Address API Overview

Every parcel in a tracking system has two addresses: a shipper (origin) and a recipient (destination). Before you can register parcels, you need a reliable way to create, retrieve, update, and delete addresses. This lesson covers the RESTful conventions and design decisions behind the Address Management API.

Why a Dedicated Address Resource?

Addresses are a first-class resource in the parcel tracking domain. A single address can be reused across many parcels. For example, a warehouse ships thousands of packages per day, all from the same origin address. Modeling addresses as their own resource means:

  • No data duplication - store each address once and reference it by ID
  • Consistent formatting - validate and normalize once, use everywhere
  • Independent lifecycle - update a phone number without touching every parcel that references it

RESTful Resource Design

The Address API follows standard REST conventions. The resource is /api/addresses and each address is identified by a unique ID.

OperationHTTP MethodRouteSuccess Status
List all addressesGET/api/addresses200 OK
Get one addressGET/api/addresses/{id}200 OK
Create an addressPOST/api/addresses201 Created
Update an addressPUT/api/addresses/{id}200 OK
Delete an addressDELETE/api/addresses/{id}204 No Content

These conventions are not arbitrary. They come from the HTTP specification and are understood by every HTTP client, load balancer, and caching layer on the internet.

Route Design Decisions

The route uses a plural noun (addresses) because it represents a collection. The {id} segment identifies a single resource within that collection. There is no verb in the route because the HTTP method already expresses the action.

1GET /api/addresses -> list
2GET /api/addresses/42 -> read one
3POST /api/addresses -> create
4PUT /api/addresses/42 -> replace
5DELETE /api/addresses/42 -> remove

Avoid routes like /api/addresses/create or /api/getAddress?id=42. These ignore the HTTP method semantics and make the API harder to discover and cache.

The Address Model

An address in the parcel tracking domain contains physical location data and contact information. Here is the entity class:

csharp
1public class Address
2{
3 public Guid Id { get; set; }
4 public string Street1 { get; set; } = string.Empty;
5 public string? Street2 { get; set; }
6 public string City { get; set; } = string.Empty;
7 public string State { get; set; } = string.Empty;
8 public string PostalCode { get; set; } = string.Empty;
9 public string CountryCode { get; set; } = string.Empty;
10 public bool IsResidential { get; set; }
11 public string ContactName { get; set; } = string.Empty;
12 public string? CompanyName { get; set; }
13 public string Phone { get; set; } = string.Empty;
14 public string? Email { get; set; }
15}

A few things to note:

  • Street2, CompanyName, and Email are nullable - not every address has an apartment number, a company, or an email
  • CountryCode is a 2-letter ISO 3166-1 alpha-2 code - "US", "CA", "GB", not the full country name
  • IsResidential distinguishes home addresses from commercial locations, which affects carrier pricing and delivery options

The DTO Pattern

The entity class maps directly to the database. But exposing it directly in API requests and responses creates problems:

  1. Over-posting attacks - a client could send an Id field in a POST body and potentially overwrite an existing record
  2. Tight coupling - if you rename a database column, every API consumer breaks
  3. Information leakage - internal fields like audit timestamps should not appear in responses

The solution is the Data Transfer Object (DTO) pattern. You create separate classes for API input and output:

csharp
1// What the client sends when creating or updating
2public class CreateAddressRequest
3{
4 public string Street1 { get; set; } = string.Empty;
5 public string? Street2 { get; set; }
6 public string City { get; set; } = string.Empty;
7 public string State { get; set; } = string.Empty;
8 public string PostalCode { get; set; } = string.Empty;
9 public string CountryCode { get; set; } = string.Empty;
10 public bool IsResidential { get; set; }
11 public string ContactName { get; set; } = string.Empty;
12 public string? CompanyName { get; set; }
13 public string Phone { get; set; } = string.Empty;
14 public string? Email { get; set; }
15}
16
17// What the API returns
18public class AddressResponse
19{
20 public Guid Id { get; set; }
21 public string Street1 { get; set; } = string.Empty;
22 public string? Street2 { get; set; }
23 public string City { get; set; } = string.Empty;
24 public string State { get; set; } = string.Empty;
25 public string PostalCode { get; set; } = string.Empty;
26 public string CountryCode { get; set; } = string.Empty;
27 public bool IsResidential { get; set; }
28 public string ContactName { get; set; } = string.Empty;
29 public string? CompanyName { get; set; }
30 public string Phone { get; set; } = string.Empty;
31 public string? Email { get; set; }
32}

The request DTO has no Id because the server assigns it. The response DTO includes Id so the client can reference the resource. In a real application the response DTO might also include CreatedAt or a computed FormattedAddress property that does not exist on the entity.

Referential Integrity

When an address is referenced by one or more parcels, deleting it would leave those parcels with a broken reference. The API must prevent this by checking for existing references before allowing deletion.

If a client tries to delete an address that is in use, the API returns 409 Conflict with a message explaining why the deletion was rejected. This is the correct HTTP status code for a request that conflicts with the current state of the resource.

1DELETE /api/addresses/42
2HTTP/1.1 409 Conflict
3
4{
5 "message": "Cannot delete address. It is referenced by 3 parcels."
6}

The client can then decide how to proceed, perhaps by reassigning those parcels to a different address first.

Input Validation Strategy

Validation happens at multiple levels:

  1. Data annotations on DTOs - catch obvious problems like missing required fields before your code even runs
  2. Custom validators - enforce business rules like postal code format per country or valid ISO country codes
  3. Database constraints - a final safety net if something slips through

The next two presentations walk through building the controller and implementing validation in detail.

Summary

The Address Management API is a textbook CRUD resource. It uses standard HTTP methods, separates the API contract from the database model with DTOs, validates input thoroughly, and protects referential integrity. These patterns apply to every resource you will build in this course.