Web API Development with ASP.NET Core 8 - Xiaodi Yan - E-Book

Web API Development with ASP.NET Core 8 E-Book

Xiaodi Yan

0,0
39,59 €

-100%
Sammeln Sie Punkte in unserem Gutscheinprogramm und kaufen Sie E-Books und Hörbücher mit bis zu 100% Rabatt.
Mehr erfahren.
Beschreibung

Web API applications have become increasingly significant in recent years, fueled by the ever-accelerating pace of technological advancements. However, with this rapid evolution comes a pressing challenge: the need to create web API applications that are not only functional but also adaptable, maintainable, and scalable to meet the demands of users and businesses alike. This book will help you address this challenge head-on, equipping you with the knowledge and skills required to develop web API applications from scratch.
By providing a deeper understanding of the various protocols implemented by ASP.NET Core, including RESTful, SignalR (WebSocket), gRPC, and GraphQL, supplemented by practical examples and optimization techniques, such as using middleware, testing, caching, and logging, this book offers invaluable insights for both newcomers as well as seasoned developers to meet modern web development requirements. Additionally, you’ll discover how to use cloud platforms such as Azure and Azure DevOps to enhance the development and operational aspects of your application.
By the end of the book, you’ll be fully prepared to undertake enterprise-grade web API projects with confidence, harnessing the latest advancements in ASP.NET Core 8 to drive innovation.

Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:

EPUB
MOBI

Seitenzahl: 939

Bewertungen
0,0
0
0
0
0
0
Mehr Informationen
Mehr Informationen
Legimi prüft nicht, ob Rezensionen von Nutzern stammen, die den betreffenden Titel tatsächlich gekauft oder gelesen/gehört haben. Wir entfernen aber gefälschte Rezensionen.



Web API Development with ASP.NET Core 8

Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs

Xiaodi Yan

Web API Development with ASP.NET Core 8

Copyright © 2024 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.

Group Product Manager: Rohit Rajkumar

Senior Editor: Nathanya Dias

Technical Editor: K Bimala Singha

Copy Editor: Safis Editing

Project Coordinator: Sonam Pandey

Indexer: Hemangini Bari

Production Designer: Aparna Bhagat

Marketing Coordinators: Anamika Singh and Nivedita Pandey

First published: March 2024

Production reference: 1010324

Published by Packt Publishing Ltd.

Grosvenor House

11 St Paul's Square

Birmingham

B3 1RB, UK.

ISBN 978-1-80461-095-4

www.packtpub.com

When I signed the contract with the publisher, I never anticipated that authoring a book would be so challenging. I owe immense gratitude to my family for their unwavering support and understanding. As I dedicated most of my weekends and holidays to writing this book, I deeply appreciate their patience and encouragement despite the limited time we shared.

My heartfelt appreciation also extends to my friends and colleagues for their constant support and motivation. There were moments when I felt overwhelmed and on the verge of giving up, but their enduring patience and willingness to listen to my frustrations kept me going. Their encouragement was invaluable, and I am truly fortunate to have them in my life.

I also want to extend my heartfelt thanks to my editors, whose patience and understanding surpassed my expectations. My tendency to procrastinate undoubtedly presented challenges and I am sincerely grateful for their guidance and persistence throughout the editing process.

Additionally, I express my gratitude to the reviewers for their invaluable feedback. Their constructive suggestions have significantly enriched the content and contributed to its overall improvement.

As I reflect on this journey, I am immensely proud to have completed this book. It has been a labor of love, fueled by my passion for .NET and the incredible possibilities it offers. I hope you will find as much joy in exploring the world of .NET through these pages as I have experienced in writing about it.

Contributors

About the author

Xiaodi Yan is a seasoned software engineer with a proven track record of success in the IT industry. Since 2015, he has been awarded Microsoft MVP, showcasing his dedication to and expertise in .NET, AI, DevOps, and cloud computing. He is also a Microsoft Certified Trainer (MCT), Azure Solutions Architect Expert, and LinkedIn Learning instructor. Xiaodi often presents at conferences and user groups, leveraging his extensive experience to engage and inspire audiences. Based in Wellington, New Zealand, he spearheads the Wellington .NET User Group, fostering a vibrant community of like-minded professionals.

Connect with Xiaodi on LinkedIn at https://www.linkedin.com/in/xiaodi-yan/ to stay updated on his latest insights.

To my family, whose unwavering support and understanding made this journey possible. Your constant support got me through those late nights and tough writing sessions.

And to you, the reader: it is my pleasure to share what I’ve learned with you. May this book inspire and empower you on your own journey in the world of ASP.NET Core.

About the reviewers

Rupenkumar Anjaria has more than 20 years of experience in architecting, designing, developing, and maintaining complex enterprise web applications. His primary technical experience includes working on .NET, C#, Angular, and SQL Server technologies, with several years of experience in ServiceNow and React, MongoDB, MVC, TFS, and Jenkins.

Aditya Oberai is a developer advocate at Appwrite and a Microsoft MVP. Having worked with various technologies, such as ASP.NET web APIs, .NET MAUI, Svelte, Node.js, and Microsoft Azure, he has spent the last five years empowering tech communities and hackathons across India and beyond. He can often be found interacting with and educating the community about APIs, serverless, cross-platform apps, community building, indie hacking, and open-source.

Ifeoluwa Osungade is a senior software engineer at Agoda, where he channels his deep expertise in C# and web development to craft software solutions that elevate the travel experience for countless customers worldwide. With an impressive career spanning over eight years in the software engineering domain, Ifeoluwa has made significant contributions across diverse industries such as technology, construction, and logistics. This breadth of experience underscores his ability to not only identify and define challenges but also create and implement innovative software solutions that streamline business operations, enhance efficiency, and boost profitability. His other areas of expertise are Scala, Python, SQL, and Azure.

Table of Contents

Preface

1

Fundamentals of Web APIs

What is a web API?

What is a REST API?

The constraints of REST

A REST API example

Is my web API RESTful?

Designing a REST-based API

Identifying the resources

Defining the relationships between resources

Identifying operations

Designing the URL paths for resources

Mapping API operations to HTTP methods

Assigning response codes

Documenting the API

RPC and GraphQL APIs

What is an RPC-based API?

What is a GraphQL API?

Real-time APIs

The problem with API polling

What is a real-time API?

Which real-time communication technology is best for your application?

Summary

2

Getting Started with ASP.NET Core Web APIs

Technical requirements

Setting up the development environment

Configuring VS Code

Checking the .NET SDK

Creating a simple REST web API project

Building and running the project

Building the project

Running the project

Changing the port number

Hot Reload

Testing the API endpoint

Swagger UI

Debugging

Understanding the MVC pattern

The model and the controller

Creating a new model and controller

Creating a service

Implementing a GET operation

Implementing a CREATE operation

Implementing an UPDATE operation

Dependency injection

Understanding DI

DI in ASP.NET Core

DI tips

Introduction to minimal APIs

Creating a simple endpoint

Using DI in minimal APIs

What is the difference between minimal APIs and controller-based APIs?

Summary

3

ASP.NET Core Fundamentals (Part 1)

Technical requirements

Routing

What is attribute routing?

Mapping HTTP methods to action methods

Route constraints

Binding source attributes

Configuration

Using appsettings.json

Using the options pattern

Other configuration providers

Environments

Understanding the launchSettings.json file

Setting the environment

Understanding the priorities of configuration and environment variables

Checking the environment in the code

Summary

4

ASP.NET Core Fundamentals (Part 2)

Technical requirements

Logging

Using built-in logging providers

Logging levels

Logging parameters

Using third-party logging providers

Structured logging

What should/shouldn’t we log?

Middleware

What is middleware?

Built-in middleware

Creating a custom middleware component

Summary

5

Data Access in ASP.NET Core (Part 1: Entity Framework Core Fundamentals)

Technical requirements

Why use ORM?

Configuring the DbContext class

Creating models

Creating and configuring the DbContext class

Creating the database

Adding seed data

Implementing CRUD controllers

Creating the controller

How controllers work

Basic LINQ queries

Querying the data

Filtering the data

Sorting and paging

Creating an entity

Updating an entity

Deleting an entity

Configuring the mapping between models and database

Mapping conventions

Data annotations

Fluent API

Separating the mapping configurations

Summary

6

Data Access in ASP.NET Core (Part 2 – Entity Relationships)

Technical requirements

Understanding one-to-many relationships

One-to-many configuration

One-to-many CRUD operations

Understanding one-to-one relationships

One-to-one configuration

One-to-one CRUD operations

Understanding many-to-many relationships

Many-to-many configuration

Many-to-many CRUD operations

Understanding owned entities

Summary

7

Data Access in ASP.NET Core (Part 3: Tips)

Technical requirements

Understanding DbContext pooling

Understanding the difference between tracking versus no-tracking queries

Understanding the difference between IQueryable and IEnumerable

Client evaluation versus server evaluation

Using raw SQL queries

FromSql() and FromSqlRaw()

SqlQuery() and SqlQueryRaw()

ExecuteSql() and ExecuteSqlRaw()

Using bulk operations

ExecuteUpdate()

ExecuteDelete()

Understanding concurrency conflicts

Native database-generated concurrency token

Application-managed concurrency token

Handling concurrency conflicts

Reverse engineering

Other ORM frameworks

Summary

8

Security and Identity in ASP.NET Core

Technical requirements

Getting started with authentication and authorization

Creating a sample project with authentication and authorization

Understanding the JWT token structure

Consuming the API

Configuring the Swagger UI to support authorization

Delving deeper into authorization

Role-based authorization

Claim-based authorization

Understanding the authorization process

Policy-based authorization

Managing users and roles

New Identity API endpoints in ASP.NET Core 8

Understanding OAuth 2.0 and OpenID Connect

What is OAuth 2.0?

What is OpenID Connect?

Integrating with other identity providers

Other security topics

Always use Hypertext Transfer Protocol Secure (HTTPS)

Using a strong password policy

Implementing two-factor authentication (2FA)

Implementing rate-limiting

Using model validation

Using parameterized queries

Using data protection

Keeping secrets safe

Keeping the framework up to date

Checking the Open Web Application Security Project (OWASP) Top 10

Summary

9

Testing in ASP.NET Core (Part 1 – Unit Testing)

Technical requirements

Introduction to testing in ASP.NET Core

Writing unit tests

Preparing the sample application

Setting up the unit tests project

Writing unit tests without dependencies

Writing unit tests with dependencies

Using FluentAssertions to verify the test results

Testing the database access layer

How can we test the database access layer?

Creating a test fixture

Using the test fixture

Writing tests for methods that change the database

Parallelism of xUnit

Using the repository pattern

Testing the happy path and the sad path

Summary

10

Testing in ASP.NET Core (Part 2 – Integration Testing)

Technical requirements

Writing integration tests

Setting up the integration test project

Writing basic integration tests with WebApplicationFactory

Testing with a database context

Testing with mock services

Testing with authentication and authorization

Preparing the sample application

Creating a test fixture

Creating the test class

Testing the anonymous API endpoints

Testing the authorized API endpoints

Code coverage

Using data collectors

Generating a code coverage report

Summary

11

Getting Started with gRPC

Technical requirements

Recap of gRPC

Setting up a gRPC project

Creating a new gRPC project

Understanding the gRPC project structure

Creating protobuf messages

Defining a protobuf message

Understanding field numbers

Understanding the field types

Other .NET types

Creating a protobuf service

Defining a unary service

Creating a gRPC client

Defining a server streaming service

Defining a client streaming service

Defining a bidirectional streaming service

Consuming gRPC services in ASP.NET Core applications

Updating proto files

Summary

Further reading

12

Getting Started with GraphQL

Technical requirements

Recap of GraphQL

Setting up a GraphQL API using HotChocolate

Adding mutations

Using variables in queries

Defining a GraphQL schema

Scalar types

Object types

Retrieving related objects using resolvers

Field resolvers

Resolver for a list of objects

Using data loaders

Batch data loader

Group data loader

Dependency injection

Using the Service attribute

Understanding the lifetime of the injected services

Interface and union types

Interfaces

Union types

Filtering, sorting, and pagination

Filtering

Sorting

Pagination

Visualizing the GraphQL schema

Summary

Further reading

13

Getting Started with SignalR

Technical requirements

Recap of real-time web APIs

Setting up SignalR

Building SignalR clients

Building a TypeScript client

Building a Blazor client

Using authentication and authorization in SignalR

Adding authentication and authorization to the SignalR server

Adding a login endpoint

Authenticating the TypeScript client

Authenticating the Blazor client

Managing users and groups

Managing events in SignalR

Sending a message to a specific user

Using strongly typed hubs

Joining groups

Sending a message to a group

Sending messages from other services

Configuring SignalR hubs and clients

Configuring SignalR hubs

HTTP configuration options

Automatically reconnecting

Scaling SignalR

Summary

14

CI/CD for ASP.NET Core Using Azure Pipelines and GitHub Actions

Technical requirements

Introduction to CI/CD

CI/CD concepts and terminologies

Understanding the importance of CI/CD

Containerizing ASP.NET Core applications using Docker

What is containerization?

Installing Docker

Understanding Dockerfiles

Building a Docker image

Running a Docker container

CI/CD using Azure DevOps and Azure Pipelines

Preparing the source code

Creating Azure resources

Creating an Azure DevOps project

Creating a pull request pipeline

Publishing the Docker image to ACR

Deploying the application to Azure Web App for Containers

Configuring settings and secrets

GitHub Actions

Preparing the project

Creating GitHub Actions

Pushing a Docker image to ACR

Summary

15

ASP.NET Core Web API Common Practices

Technical requirements

Common practices of ASP.NET web API development

Using HTTPS instead of HTTP

Using HTTP status codes correctly

Using asynchronous programming

Using pagination for large collections

Specifying the response types

Adding comments to the endpoints

Using System.Text.Json instead of Newtonsoft.Json

Optimizing the performance by implementing caching

In-memory caching

Distributed caching

Response caching

Output caching

What caching strategy should I use?

Using HttpClientFactory to manage HttpClient instances

Creating a basic HttpClient instance

Named HttpClient instances

Typed HttpClient instances

Summary

16

Error Handling, Monitoring, and Observability

Technical requirements

Error handling

Handling exceptions

Health checks

Implementing a basic health check

Monitoring and observability

What is observability?

Summary

17

Cloud-Native Patterns

Technical requirements

Domain-driven design

Ubiquitous language

Bounded context

DDD layers

Clean architecture

Microservices

Web API design patterns

CQRS

Summary

Further reading

Index

Other Books You May Enjoy

1

Fundamentals of Web APIs

In today’s world, web APIs are the backbone of the web. Millions of people use web APIs every day to purchase commodities, book a flight, get weather information, and more. In this chapter, we will learn about the fundamentals of web APIs. You might be wondering why we will start with the fundamental concepts. The answer is simple – we need to understand the basic concepts of web APIs before we build one.

This chapter introduces a couple of different web API styles, such as a REST-based API, a remote procedure call (RPC)-based API, a GraphQL API, and a real-time API. We will also learn about how to design them. If you would like to start developing a web API, feel free to jump to the next chapter.

In this chapter, we’ll be covering the following topics:

What is a web API?What is a REST API?Designing a REST-based APIWhat are RPC and GraphQL APIs?What is a real-time API?

After reading this chapter, you will have a basic understanding of web APIs and be able to pick the right style for your project. Let’s get started!

What is a web API?

API stands for application programming interface. A web API, as the name suggests, is a set of programming interfaces for the web. For example, when you book a flight on a website, the browser makes a request to the airline’s server through a web API to access the airline’s database. The airline’s server then returns the information about the flight to the browser, allowing you to book your flight in it.

APIs have been delivered by organizations for decades. With the appearance of the World Wide Web, people needed a way to communicate between the server and the client.

We can build web APIs using different technologies, such as Java, Python, Ruby, PHP, .NET, and so on. Also, they have various styles. You might have heard of terms such as SOAP, Web Service, and REST. They are all based on the HTTP protocol but communicate in different ways.

In this book, we consider web APIs as a wider concept than REST. In the digital world, the way machines communicate with each other changes as either the demands or the infrastructure evolves. In the 1990s, people focused on how to improve the internal networks that used the same platforms. TCP/IP came to be the standard for this kind of communication. After a few years, people needed to find a way to optimize communication across multiple platforms. Web Services appeared, and they used the Simple Object Access Protocol (SOAP), which was defined for enterprises, and it ensured that programs built on different platforms could easily exchange data.

However, SOAP XML is quite heavy, which means it requires more bandwidth for its usage. In the early 2000s, Windows Communication Foundation (WCF) was released by Microsoft. This helped developers manage the complexities of working with SOAP. WCF is RPC-based but still uses SOAP as the underlying protocol. Over time, some old standards, such as SOAP, have been transitioned to REST APIs, which will be discussed in the next section. We will start with REST APIs and then move on to the other styles of web-based APIs, such as gRPC APIs, GraphQL APIs, and SignalR APIs.

What is a REST API?

REST, also known as Representational State Transfer, is an architectural style of web APIs that was created by Roy Fielding in his Ph.D. dissertation Architectural Styles and the Design of Network-based Software Architectures in 2000. Today, generally speaking, REST APIs are based on HTTP, but actually, Roy Fielding’s paper just outlines the core concepts and constraints for understanding an architectural style, and it does not require any specific protocol for REST-based architecture, such as HTTP. However, since HTTP is the most widely used protocol for web APIs, we will use HTTP as the protocol for REST APIs.

Just keep in mind that REST is just a style, not a rule. When you build a web API, you do not have to follow the REST style. You can use any other style you like. You can build a web API that works well, but it might not be REST enough. REST is the recommended style because it helps us establish constraints, which contribute to the design of web APIs. It also helps developers easily integrate with other REST APIs if they follow the same style.

The core concept of REST is the term representational state transfer. Think about a web system, which is a collection of resources. For example, it might have a resource called books. The collection of books is a resource. A book is a resource too. When you request the list of the books, you select a link (for example, http://www.example.com/books), which will return a JSON string that contains all the books, resulting in the next resource’s representation, such as the link of a specific book (for example, http://www.example.com/books/1). You can continue to request the book with this link. In this process, the representation state is transferred to the client and rendered for the user.

There are loads of resources that explain REST. If you would like to know more about REST, you can read the following article on Wikipedia: REST: The Web Framework for Representational State Transfer (https://en.wikipedia.org/wiki/Representational_state_transfer).

Let’s take a look at the constraints of REST, following which we will show you a simple example of REST APIs.

The constraints of REST

Roy Fielding’s paper defines the following six constraints for REST APIs:

Client-server: This pattern enforces the principle of separation of concerns. The server and the client act independently. The client sends the request and the server responds, following which the client receives and interprets the response. The client does not need to know how the server works, and vice versa.Statelessness: The server does not maintain any state of the client. The client should provide the necessary information in the request. This stateless protocol is important to scale out the capacity of the server because it does not need to remember the session state of the clients.Cacheability: The response of the server must implicitly or explicitly contain information about whether the response is cacheable, allowing the client and intermediaries to cache the response. The cache can be performed on the client machine in memory, in browser cache storage, or in a content delivery network (CDN). It is also important to improve the scalability and performance of web APIs.The layered system: The client does not know how it is connected to the server. There may be multiple layers between the client and the server. For example, a security layer, a proxy, or a load balancer can be placed between the client and the server without impacting the client or server code.Code on demand (optional): The client can request code from the server for client-side use. For example, the web browser can request JavaScript files to perform some tasks.Uniform interface: This one is essential for a RESTful system. It contains resource identification, resource manipulation through representations, self-descriptive messages, and hypermedia as the engine of the application state. It simplifies and decouples the architecture of the system, which enables each part to evolve independently.

If you feel these principles are a little bit distant or theoretical, let’s look at an example.

A REST API example

The website https://jsonplaceholder.typicode.com/ is a fake REST API that generates fake JSON data. Open the following link in your browser: https://jsonplaceholder.typicode.com/posts. You will see a JSON string returned:

[  {     "userId": 1,     "id": 1,     "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",     "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"   },   {     "userId": 1,     "id": 2,     "title": "qui est esse",     "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"   }, ... ]

From the preceding request, we can get the resource for the collection of the posts.

Now, we can request a specific post by its ID. For example, we can request the post with ID 1 using the following URL: https://jsonplaceholder.typicode.com/posts/1. The response is as follows:

{  "userId": 1,   "id": 1,   "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",   "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto" }

And that’s it! The URLs we used in the preceding examples are the identifiers of the resources. The responses (the JSON strings) are the representations of the resources. A resource is manipulated through hypertext representations that are transferred in messages between clients and servers.

Important note

Some documents use URI. A Uniform Resource Identifier (URI) is a unique sequence of characters that identifies a logical or physical resource used by web technologies. It might use a location, name, or both. A Uniform Resource Locator (URL) is a type of URI that points to a resource over a network. It defines its protocol, such as http, https or ftp. Nowadays, the term URL remains widely used, so, we will use that in this book. However, we should know they have different scopes. URI is the superset of URL.

To get a post resource, we send a GET request. There are some other methods for manipulating resources, such as POST, PUT, PATCH, and DELETE, as shown here:

HTTP method

URL

Operation

Description

GET

/posts

Read

Read the collection of the posts

GET

/posts/1

Read

Read a post by its ID

GET

/posts/1/comments

Read

Read the comments of the post

POST

/posts

Create

Create a new post

PUT

/posts/1

Update

Update a post by its ID

PATCH

/posts/1

Update (partial)

Update part of a post by its ID

DELETE

/posts/1

Delete

Delete a post by its ID

Table 1.1 – HTTP methods and URLs for manipulating resources

There are other methods that are less frequently used, such as HEAD, OPTIONS, and TRACE.

As we can see, the HTTP methods are mapped to the create, update, read, and delete (CURD) operations. But was it always this way?

Is my web API RESTful?

As already mentioned, REST is not a rule or a specification. There is no official standard for REST APIs. Contrary to popular opinion, it does not require JSON. Furthermore, it does not require the use of CRUD patterns. But REST implementation does make use of standards, such as HTTP, URL, JSON, XML, and so on. People apply HTTP methods and JSON to implement REST, but they may not intentionally apply the constraints as originally described in Fielding’s paper. This leads people to disagree on whether their APIs are RESTful or not. Many developers describe their APIs as RESTful, even though these APIs do not satisfy all of the constraints described in Fielding’s paper.

Frankly, it is not beneficial to argue whether a web API is REST enough or not. The goal is to make something work, rather than wasting time on a discussion of this kind of problem. Not everyone has read the original paper. Technology also evolves rapidly. There is a Chinese saying: It doesn’t matter whether it is a white cat or a black cat; as long as it catches mice, it is a good cat.

However, it would be ideal if we follow conventions when we start a greenfield project. Generally, a REST-based API is defined with the following aspects:

A base URL, which is the root of the API, such as http://api.example.com.Semantics of HTTP methods, such as GET, POST, PUT, DELETE, and so on.A media type, which defines state transition data elements, such as application/json, application/xml, and so on.

In this book, we will try to follow these conventions when we develop the REST APIs with ASP.NET Core.

Now that we have had an overview of a REST API, let’s see how to design one following the conventions.

Designing a REST-based API

To build a REST-based API, there are many steps to take before we write code. The development team needs to communicate with stakeholders and analyze the requirements. Then, they need to write user stories (or job stories) to define the desired outcomes. This requires the insights of domain experts or subject matter experts. We will not cover this part in this book. Instead, next, we will focus on the API design, which is closer to what developers do.

In the past few years, the concept of API-first has gained more traction. The API-first approach means that the APIs are treated as first-class citizens for your project. This creates a contract for how the API is supposed to behave before any code is written. In this way, the development teams can work in parallel because the contract will be established first. Developers do not have to wait for the API to be released before integrating with frontend or mobile apps. They can mock and test the APIs based on the contract. Using tools such as Swagger, the process of building APIs can be automated, such as API documentation, mock APIs, SDKs, and so on. Automation can significantly speed up the development of APIs and applications, which helps to increase the speed to market.

Here are some steps we can follow to design a REST-based API:

Identify the resources.Define the relationships between resources.Identify operation events.Design the URL paths for resources.Map API operations to HTTP methods.Assign response codes.Document the API.

If you are familiar with the preceding steps, you can skip them. However, if you are not, read the following sections.

A popular API description format is OpenAPI Specification (OAS). We can use it to describe API modeling and other details of an API. We do not need to include the implementation details at this stage, because we just want to make a contract. SwaggerHub (https://app.swaggerhub.com/home) is a tool we can use to design an API.

Identifying the resources

REST-based APIs center on resources. A resource is a collection of data, such as a collection of posts or a collection of users. A resource is identified by a URL. The client requests a resource using the URL, and the server responds with a representation of the resource. The representation of the resource is sent in the hypertext format, which can be interpreted by the widest possible range of clients.

It is important to identify the scope of the domain and the relationships between the resources. For example, if you are building a blog system, you may have a collection of posts, and each post has a collection of comments. The scope of an API may evolve as time goes by. More resources may be added to the current domain, or some resources will be removed. Also, relationships may change.

Let’s start small. We can use the blog system as an example. After the requirement analysis, we can identify the following resources:

PostsCategoriesCommentsUsersTags

You may want to include some properties of each resource in this step. For example, a post has a title, a body, and a published datetime. A comment has a body, a publish datetime. A user has a name and an email. You may find more properties during development.

Defining the relationships between resources

Once the resources are identified, we can define the relationships between them. For example, a post has a collection of comments. A comment has a post. A user has a collection of posts.

The relationship is defined by how these resources relate to each other. Sometimes, these relationships exist in the database as well, but sometimes, they are specific to the REST resources only.

There are some terms we can use to describe the relationships:

Independent resource: This resource can exist independently. It does not require another resource to exist. An independent resource can reference other independent or dependent resources. For example, a post is an independent resource, and it can reference its author. The authors resource is also an independent resource.Dependent resource: This resource requires another resource to exist. It can still reference other independent or dependent resources, but it cannot exist without the existence of the parent resource. For example, a comment requires a post as its parent resource; otherwise, it cannot exist. A comment can reference its author, which is an independent resource.Principal key (or primary key): This resource has a unique identifier. It may be the primary key of the resource in the database. For example, a post has a Id property, which can uniquely identify itself.Foreign key: This dependent resource has a property to reference the principal resource, which stores the principal key of the principal resource. For example, a comment has a PostId property, which references a post.

There are three relationship types that these resources can have:

One-to-many: This is when a resource has many related resources. For example, a user has many posts, but a post has only one author. This is also called the parent-child (children) relationship, which is the most common pattern for relationships we can see in the REST-based API world.One-to-one: This is when a resource has one related resource. For example, a post has a single author, and one house has only one address. The one-to-one relationship is a special case of the one-to-many relationship.Many-to-many: This is when a resource has many related resources and vice versa. For example, a blog has many tags, and a tag has many blogs. A movie can have many genres, and a genre can have many movies. In many systems, a user can have many roles, and a role can have many users.

Identifying operations

Next, we can think about what operations are needed for each resource. These operations may come from user stories that are defined beforehand. Generally, each resource has its CRUD operations. Note that the operations may include more beyond CRUD. For example, a post can be published, or it can be unpublished. A comment can be approved, or it can be rejected. During this process, we may need to create a new resource or property to reflect the operation.

It is important to consider the scope of the domain. CRUD operations are easy to understand, but for some complicated relationships, we may need help from domain experts.

When we work on these operations, we need to include important input and output details. We will use them in the next steps. However, it is not necessary to include all the details of each resource. We have enough time later to capture the complete design.

For the example of the blog system, we can identify these operations for the Post resource (including but not limited to the following):

Operation name

Resource(s)

Input

Output

Description

createPost()

Post, category, user, tag

Post detail

Create a new post

listPosts()

Post

A list of posts

List all posts

listPostsByCategory()

Post and category

Category ID

A list of posts

List posts by category

listPostsByTag()

Post and tag

Tag or Tag ID

A list of posts

List posts by tag

searchPosts()

Post

Search keyword

A list of posts

Search for Posts by title, author, and content

viewPost()

Post, category, and user

Post ID

Post detail

View a post detail

deletePost()

Post

Post ID

Delete a post

updatePost()

Post and category

Post detail

Update a post

publishPost()

Post

Post ID

Publish a post

unpublishPost()

Post

Post ID

Unpublish a post

Table 1.2 – Operations for the Post resource

For some operations, such as createPost and deletePost, the output is the results of the operation. This can be represented with the HTTP status code. We will discuss this later.

We can list more operations for other resources as well.

Designing the URL paths for resources

The next step is to design the URL paths for each resource. The clients use URLs to access the resources. Even though REST is not a standard, there are some guidelines or conventions for designing URL paths.

Using nouns instead of verbs

The operation events we identified in the previous step are some actions, such as Create, List, View, Delete, and so on. However, the URL paths are not usually presented by verbs. Because HTTP methods such as GET, POST, PUT, and DELETE are already verbs, it is not necessary to include verbs in the URL paths. Instead, we should use nouns to represent the resources – for example, /posts.

Using plural nouns to represent collections

If a resource is a collection, we should use plural nouns to represent the resource. For example, /posts is the URL path for the collection of posts. To get a single post by its ID, we can use /posts/{postId}.

Using logical nesting to represent relationships

For the resources that have a relationship, normally, the child resource (i.e., the dependent resource) should be nested under the parent resource, and the path should include the parent identifier. However, this does not reflect the database structure. For example, a post can have a collection of comments; the URL looks like /posts/{postId}/comments. It clearly shows that the comments are related to the post.

However, if the relationships are too deep or complicated, the nesting URL path can be too long. In this case, we can rethink how to better represent those resources. For example, if we want to retrieve an author’s information from one comment, we could use /posts/{postId}/comments/{commentId}/author. But this goes too far. Instead, if we know the UserId of the author, we can use /users/{userId}. Avoid using deep nesting in URL paths because it makes an API more complicated and not readable.

Allowing filtering, sorting, and pagination

Returning all records simultaneously is not a good idea. We can use filtering, sorting, and pagination to return a subset of the records that a client needs. These operations can improve the performance of the APIs and provide a better user experience.

For example, if we want to search a list of posts for a specific keyword, we can use a query parameter, such as /posts?search=keyword. If we want to sort posts by the date, we can use /posts?sort=date. To get the second page of the posts, we can use /posts?page=2. These query parameters can be combined witheach other.

What if I cannot find a proper verb in HTTP methods for an operation?

Generally, HTTP methods can represent CRUD operations. However, in the real world, there are many more complexities! For example, besides the basic CRUD operations, there are other operations, such as publishing or unpublishing a post. So, what HTTP methods should we use?

This is where things can get tricky. This subject is open to debate, but remember that we are not arguing whether an API is RESTful enough. We just want to make it work.

There are different approaches for these scenarios:

One possible solution could be treating such operations like a sub-resource. So, you can use /posts/{postId}/publish to publish a post. GitHub uses the following URL to star a gist: /gists/{gist_id}/star. For more information, check out https://docs.github.com/en/rest/gists/gists#star-a-gist.The post should have an IsPublished field to indicate whether it is published. So, actually, the publish action is an update action, which updates the IsPublished field only. Then, you can treat it the same as the updatePost() operation.

Here are some resource URLs for the blog system:

Operation name

URL

Input

Output

Description

createPost()

/posts

Post detail

Create a new post

listPosts()

/posts

A list of posts

List all posts

listPostsByCategory()

/posts?categoryId={categoryId}

Category ID

A list of posts

List posts by category

listPostsByTag()

/posts?tag={tagId}

Tag or Tag ID

A list of posts

List posts by tag

searchPosts()

/posts?search={keyword}

Search keyword

A list of posts

Search for posts by title, author, and content

viewPost()

/posts/{postId}

Post ID

Post detail

View a post detail

deletePost()

/posts/{postId}

Post ID

Delete a post

updatePost()

/posts/{postId}

Post detail

Update a post

publishPost()

/posts/{postId}/publish

Post ID

Publish a post

unpublishPost()

/posts/{postId}/unpublish

Post ID

Unpublish a post

Table 1.3 – URLs for the Post resource

Some URLs are identical, such as deletePost() and updatePost(), because we will use HTTP methods to differentiate those operations.

Mapping API operations to HTTP methods

Next, we need to identify which HTTP method is appropriate for each operation. As we mentioned before, there are some common HTTP methods for CRUD operations. For example, when we request a resource, we should use the GET method. When we create a new resource, we should use the POST method.

When we map the API operations to HTTP methods, we also need to consider the safety of the operations and HTTP methods. There are three types of safety for HTTP operations:

Safe: The operation is safe. It does not change any state of the resource. For example, if we send a GET request to /posts/{postId}, it will return the same result, no matter how many times the same request is sent. For some cases, the resource might be updated by a third party, and the next GET request will return the updated result. But this was not caused by the client, so it is important to understand whether the state change is caused by the client who sent the request.Idempotent: The operation is idempotent, which means it makes state changes to the target resource, but it will produce the same result if the same input is provided. Note that idempotency does not mean that the server must return the same response. For example, if we want to delete a post, we can send a DELETE request to /posts/{postId} to delete it. If the request is a success, we will get a 200 OK or a 204 No Content response. If we send the same request to /posts/{postId} again, it may return a 404 Not found response because the resource was already deleted, but it will not cause any other side effects. If an operation is idempotent and the client knows whether the previous request failed, it is safe to reissue the request without any side effects.Unsafe: The operation is unsafe. It makes state changes to the target resource. If the request is sent again, it cannot guarantee the same result. For example, we can send a POST request to /posts to create a new post. If we send the same POST request again, it will create another new post with the same title and content, and so on.

All safe methods are also idempotent, but not all idempotent methods are safe. The following table lists the safety of each HTTP method:

HTTP method

Safe

Idempotent

Common operations

GET

Yes

Yes

Read, list, view, search, show, and retrieve

HEAD

Yes

Yes

HEAD is used to check the availability of a resource without actually downloading it.

OPTIONS

Yes

Yes

OPTIONS is used to retrieve the available HTTP methods for a given resource.

TRACE

Yes

Yes

TRACE is used to get diagnostic information about a request/response cycle.

PUT

No

Yes

Update and replace

DELETE

No

Yes

Delete, remove, and clear

POST

No

No

Create, add, and update

PATCH

No

No

Update

Table 1.4 – Safety of HTTP methods

The following table shows how operations are mapped to HTTP methods:

Operation name

URL

HTTP method

Description

createPost()

/posts

POST

Create a new post

listPosts()

/posts

GET

List all posts

listPostsByCategory()

/posts?categoryId={categoryId}

GET

List posts by category

listPostsByTag()

/posts?tag={tagId}

GET

List posts by tag

searchPosts()

/posts?search={keyword}

GET

Search for posts by title, author, and content

viewPost()

/posts/{postId}

GET

View a post detail

deletePost()

/posts/{postId}

DELETE

Delete a post

updatePost()

/posts/{postId}

PUT

Update a post

publishPost()

/posts/{postId}/publish

PUT

Publish a post

unpublishPost()

/posts/{postId}/unpublish

PUT

Unpublish a post

Table 1.5 – Mapping HTTP methods for the Post resource

You may have seen some other cases, such as using POST to update a resource. That works, but it does not follow the HTTP standard. Generally speaking, we can state the following:

GET is used to read resources.POST is used to create child resources with a server-defined URL, such as /posts.PUT is used to create or replace the resource with a client-defined URL, such as /posts/{postId}.. In many cases, PUT can also be used to update a resource.PATCH is used to update parts of the resource with a client-defined URL, such as /posts/{postId}.

Assigning response codes

It is time to assign the HTTP response codes for the operations. There are some main response code categories:

2xx codes – success: The action requested by the client was received, understood, and accepted.3xx codes – redirection: The client must take additional action to complete the request. It is often used to indicate that the client should be redirected to a new location.4xx code – client errors: The operation was not successful, but the client can try again.5xx codes – server errors: The server has encountered an error or is incapable of performing the request. The client can retry in the future.

A common issue is that some developers invent their own response codes. For example, if we create a new post, we expect the server to return a 201 Created response code. Some developers may use 200 OK and include a status code in the response body. This is not a good idea. There are many layers between the server and the client. Using your own codes will probably cause problems for these middleware components. Make sure to use the right code for the right reason. Here are some common response codes:

HTTP response code

Description

200 OK

The standard response for successful HTTP requests.

201 Created

The request has been fulfilled, resulting in a new resource being created.

202 Accepted

The request has been accepted for processing, but processing has not been completed.

204 No Content

The server has successfully processed the request but does not return any content. This is common for delete operations.

400 Bad Request

The server cannot understand or process the request due to a client error, such as malformed syntax, a request size too large, or invalid input. The client should not repeat the request without modifications.

401 Unauthorized

The request requires user authentication.

403 Forbidden

The server understood the request but is refusing action. This may be due to the fact that the client does not have the necessary permissions or is attempting a prohibited action.

404 Not Found

The requested resource could not be found.

500 Internal Server Error

A generic error message. The server encountered an unexpected condition, so it cannot process the request, and no more specific messages are suitable at this time.

503 Service Unavailable

The server is currently unable to handle the request due to temporary overloading or maintenance of the server. The response should contain a Retry-After header if possible so that the client can retry after the estimated time,

Table 1.6 – Common HTTP response codes

Here is the table that shows the response codes for each operation:

Operation name

URL

HTTP method

Response

Description

createPost()

/posts

POST

Post, 201

Create a new post

listPosts()

/posts

GET

Post[], 200

List all posts

listPostsByCategory()

/posts?categoryId={categoryId}

GET

Post[], 200

List posts by category

listPostsByTag()

/posts?tag={tagId}

GET

Post[], 200

List posts by tag

searchPosts()

/posts?search={keyword}

GET

Post[], 200

Search for posts by title, author, and content

viewPost()

/posts/{postId}

GET

Post, 200

View a post detail

deletePost()

/posts/{postId}

DELETE

204, 404

Delete a post

updatePost()

/posts/{postId}

PUT

200

Update a post

publishPost()

/posts/{postId}/publish

PUT

200

Publish a post

unpublishPost()

/posts/{postId}/unpublish

PUT

200

Unpublish a post

Table 1.7 – Response codes for the Post resource

It is essential to utilize the correct response code in order to prevent any misunderstandings. This will ensure that all communication is clear and concise, thus avoiding any potential confusion.

What if I want to create my own status codes?

Technically, you can create your own status codes, but in practice, please stick as closely to the standards as possible. If you invent your own status codes, that would be risky. Your users might be in trouble consuming your APIs because they do not know your status codes. You should think about what the benefits are to have your own status codes. The convention is to respect the HTTP status codes defined in RFC. Before you create your own status codes, make sure you check the list of HTTP status codes first. Do not create your own status code unless you have strong reasons. You can find more information here: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes.

However, there might be some special situations where you want to indicate a more specific status in the response. For example, you might have an API that can process a task, but it might fail for different reasons. You might want to indicate a more detailed message in the response to let your users know what happened, rather than returning a common 4xx code. You should think about the business logic carefully and differentiate between HTTP status codes and business status codes. If you cannot find a proper code in the HTTP status codes, and you do want to show a business-related status in the response, you can choose the HTTP status code to indicate the category of the response, and then attach a response body that contains your business status code. For example, you can return a response as shown here:

400 Bad Request{ "error_code": 1, "message": "The post is locked and cannot be updated."}

So, the HTTP status code represents the common status of the operation, and in the response body, you can include some information that is specific to your system. We will discuss how to handle errors using the Problem Details object in Chapter 16.

Documenting the API

OpenAPI is a popular REST API specification. It is a programming language-agnostic interface description for REST APIs, allowing both humans and computers to discover and understand the capabilities of a service without access to source code. Similar to an interface, it describes the inputs and outputs of an API, as well as how they should be transmitted. It is also known as the Swagger specification.

Swagger versus OpenAPI

Sometimes, Swagger and OpenAPI are used interchangeably. The Swagger project was developed in early 2010s to define a simple contract for an API that contains everything needed to produce or consume an API. It was donated to the OpenAPI initiative in 2015. So, OpenAPI refers to the API specification, and Swagger refers to the open-source and commercial projects from SmartBear, which work with the OpenAPI specification. In short, OpenAPI is a specification, and Swagger is tooling that uses the OpenAPI specification. Swagger UI is also one of the Swagger tools. At the time of writing, the latest version of OpenAPI was 3.1.0.

We can use SwaggerHub to design an API based on the previous steps. Here is an example, which defines a simple API for a blog system:

openapi: 3.0.0servers:   - description: SwaggerHub API Auto Mocking     url: https://virtserver.swaggerhub.com/yanxiaodi/MyBlog/1.0.0 info:   description: This is a simple API   version: '1.0.0'   title: Sample Blog System API   contact:     email: [email protected]   license:     name: Apache 2.0     url: 'http://www.apache.org/licenses/LICENSE-2.0.html' tags:   - name: admins     description: Secured Admin-only calls   - name: developers     description: Operations available to regular developers paths:   /posts:     get:       tags:         - developers       summary: searches posts       operationId: searchPost       description: |         By passing in the appropriate options, you can search for         available blogs in the system       parameters:         - in: query           name: searchString           description: pass an optional search string for looking up post           required: false           schema:             type: string         - in: query           name: skip           description: number of records to skip for pagination           schema:             type: integer             format: int32             minimum: 0         - in: query           name: limit           description: maximum number of records to return           schema:             type: integer             format: int32             minimum: 0             maximum: 50       responses:         '200':           description: search results matching criteria           content:             application/json:               schema:                 type: array                 items:                   $ref: '#/components/schemas/Post'         '400':           description: bad input parameter

The other file of this file has been omitted.

The preceding API documentation is a YAML file, which defines two models (resources) – Post and Category – and two operations – GET for searching posts and POST for creating a new post. For each operation, there are details about the input and output, including the expected response codes.

After the API design is done, we can share the API documentation with other developers for integrations, as shown here:

Figure 1.1 – The SwaggerHub UI

Note that you might need to add more properties, based on your user stories and domains, before you share the API documentation with other teams. The API contract should be quite stable; otherwise, it will impact the consumers.

We have explained how to design a REST API. If you would like to learn how to start developing with ASP.NET Core, you can move on to Chapter 2.

REST API is one of the most popular API styles. In the next section, we will introduce other API styles, such as RPC APIs, GraphQL APIs, and real-time APIs.

RPC and GraphQL APIs

While REST-based APIs are widely used in many scenarios today, it is not the only style of web API. For some scenarios, RPC-based APIs or GraphQL APIs may be better suited. It is important to understand the advantages and disadvantages of each style of API so that you can choose the right styles for your scenarios.

What is an RPC-based API?

RPC has