Building consistent web api responses

This blog post will be about importance of building consistent web api responses. This first part will be almost language agnostic, but if you want to see real implementation of building consistent response from ASP.NET Web Api you can read second part.

When you read about building successfull RESTfull web service you always read about building intuitive, predictable URLs which contains nouns instead of verbs, for example, myapi.com/objects/{objectId}/images for getting all images that belong to particular object. Intuitively, information about one particular object would return myapi.com/objects/{objectId} URL. List of all objects would return myapi.com/objects URL. For each of them you would initiate GET request. If you want to make endpoint that creates new object in RESTfull nature you would need to make endpoint that accepts POST request to myapi.com/objects/ URL. It makes client who implements your API happier about it because you are following some standard. I often see good, predictable URL structures, and APIs that follow REST when we talk about URLs, logical use of HTTP verbs ( GET for getting information, POST for insterting new data and similar. However, what I don’t see so often, and what I think it makes client code much more elegant is building consistent web api response.

Returned status code is 200. This API is not perfect, like any, error can happen and in that case response returned to the client looked liked this:

"Error happened" . Returned status code is 500.

myapi.com/objects/12345-6 -returns data about one object. Response status code is 200, and response looks like this:

1

2

3

4

5

6

7

8

{

count:1,

data:{

objectId:"12345-6",

name:"First object"

}

}

When error(s) happen this response is returned with status code 500:

1

2

3

4

5

6

7

{

"statusCode":"500",

errors:[

{userMessage:"Unable to retrieve object",errorCode:1}

]

}

Analysis of responses

Positive thing about examples above is that at least correct HTTP status code is returned so client code can appropriately handle response and diffirentiate between success and failure. However, there are few problems:

If you want to return more data in case of failure (instead of generic “Error happened” message ) you can’t do it without breaking client’s code. Client expects string, and you would like to return object that contain more that data, for example:

1

2

3

4

{

"errorMessage":"Database error",

"errorCode":"7"

}

First takeway from example is above. Don’t return simple data types, wrap response inside some kind of object for making client’s code less brittle.

This was fixed in second example (receiving data about one object). Creator of web api created completely new object that contain statusCode property and errors property. Error property is object which contains userMessage and errorCode property. Nice thing about that is that error object is extensible. Creator of web api can add additional property as necessary. However, if we compare error responses from myapi.com/objects/ and myapi.com/objects/{objectId} we can see they are inconsistent. Not only client must refer to your documentation more often but you are also forcing your client to write more code. More code because developer who makes integration of your API must create model class for each response. Imagine, ListOfObjectsErrorResponseDto and OneObjectErrorResponseDto . There are three takeaways from this part of article. Make error response consistent.Make each property that you know in advance that will be extended as object instead of simple data type. (see Error property). Don’t force developer who implements your api to read more of your documentation than necessary and to write more code than necessary.

Let’s fix this API responses

Let’s try to put this together in consistent way following best practices from above.

This is response for getting list of all objects (HTTP status code is still 500 -because server error happened)

1

2

3

4

{

statusMessage:"Failure",

errors:[{userMessage:"Unable to retrive list of objects",errorCode:1}]

}

Response from receiving one object is:

1

2

3

{statusMessage:"Failure",

errors:[{userMessage:"Unable to retrive list of objects",errorCode:1},{userMessage:"Some other message"}]

}

Now, responses are predictable, consistent. Client can learn your logic behind your API more quickly because you only have one type of object. Client can now write only one error response model, and therefore can only have few lines of code that makes deserialization of your error responses. If you want to add more metadata to your responses you can – client’s code won’t break only because you added one additional property to your responses.

More analysis

Let’s go step further and discuss successfull responses. In first case, by visiting myapi.com/objects/ we are getting back some array -list of objects. In second case, we are getting only one object. At first glance, you are probably see that “extensibility” problem. What if we want to add additional data to list of objects. Maybe we want to implement paging and would like to return number of objects in total. If we want to do something like this it requires big changes to client’s code. This is ObjectListResponseDto :

1

2

3

4

5

6

7

8

9

10

11

{

count:267,

objects:[

{

objectId:"12345-6",name:"FirstObject"

},

{

objectId:"12346-6",name:"SecondObject"

}

]

}

If we know we are returning list of objects it would probably be good idea to return object that contains list of object as one property of that object leaving possibility to add additional properties to that object open( like above). If we know we are returning one object it is probably bad idea to return just that object. This way, you can’t easily attach metadata about response. Instead wrap it inside some object ( ObjectResponseDto ).

1

2

3

4

5

{

someAdditionalMetadataProperty:"",

object:{

objectId:"12345-6",name:"First object"

}

There is consistency problem left unresolved. At first, you may think that ObjectResponseDto and ObjectListResponse don’t have anything in common (properties are obviously different) and you are right. However, we can achieve that responses from myapi.com/objects and myapi.com/objects/{objectId} are the same. Benefit of this is that makes client’s code that does deseralization of JSON response much easier and elegant because it can expect some structure always. Let’s see that generic response structure:

In case of success response while receiving list of object we are getting back complex object that contains data property. data property is of type ObjectListResponse , so you generic response class would look like this:

1

2

3

4

5

publicclassGenericResponse{

ObjectListResponsedata{get;set;}

}

In success scenario of getting one object we will receive object that contains data property (just like in case of receving list of objects).

1

2

3

4

5

6

7

{

data:{

someAdditionalMetadataProperty:"",

object:{

objectId:"12345-6",name:"First object"

}

}

If your language supports generics like C# than your Generic response class can look like this:

1

2

3

4

5

publicclassGenericResponse<T>{

Tdata{get;set;}

}

This way client’s code is much more elagant. GenericResponse is the only model it has to deserialiaze. Takeaway from this. Make consistent web api success responses my making them all return some structure. All difference is inside data property. Data property is sometimes of type ObjectResponse, sometimes is of type ObjectListResponse.

Making same response for success and failure -building consistent web api responses

Remember error response from few paragraphs above? Here it is again:

1

2

{statusMessage:"Failure",

errors:[{userMessage:"Unable to retrive list of objects",errorCode:1}]}

If we add those properties to GenericResponse class we could make model that is returned always -in case of different response models, in case of success and failure.

1

2

3

4

5

6

7

publicclassGenericResponse<T>{

publicsuccessMessage{get;set;}

publicTdata{get;set;}

publicList<Error>errors{get;set;}

}

Now in case of failure, it is returned 403 (Bad request), 500 or other >400 status code. Errors property is populated with errors and data property is null. In case of success errors property is empty and data property is populated with appropriate data. Result of making consistent web api response:

Integration of web service takes less time because developer writes less code , refers to documentation less often

More elagant code. By taking advantage of advanced language features like generics and the fact that responses are consistent developer can make code that is easier to write and easier to maintain.

Happier costumers – APIs are built for business and costumers. All things from mentioned above (less time to integration, easirer maintaince, less bugs) result in happier costumers who use you application.

In second part of this series of articles about building consistent web api responses I will explain and show how to build consistent Web Api response using C# and ASP.NET Web Api.