This is the second blog in our Style Guide Rulebook Series, for the first part, go here.
REST APIs are designed around resources, which are any kind of object, data, or service that can be accessed by the client. A resource has an identifier, which is a URL that uniquely identifies that resource. Having consistent and robust URLs – will prove one of the best design decisions to build a good developer experience.
A URL consists of the following parts:
Here are some style guide rules that you can create and enforce using spectral to ensure best practices and consistency within API URLs while designing APIs.
Protocol
Always use HTTPS
APIs must use HTTPS protocol unless being called in a local environment i.e. localhost
Example Rule (OAS3.x)
oas3-always-use-https:
given: $.servers[*].url
then:
function: pattern
functionOptions:
match: http://localhost)|(https).*
message: Servers must use the HTTPS protocol except when using localhost
formats: oas3
severity: error
oas2-always-use-https:
given: $.schemes.*
then:
function: enummeration
functionOptions:
values:
- https
message: Servers must use the HTTPS protocol
formats: oas2
severity: warn
Host
Must be lowercase
URLs are case sensitive but it is bad practice for them to differ based only on capitalization. This can cause a lot of confusion among developers. It is usually expected for URLs to be lowercase.
server-lowercase:
given: $.servers[*].url
then:
function: pattern
functionOptions:
match: ^[^A-Z]*$
message: Server URL must be lowercase
formats: oas3
severity: error
Structure
There are multiple conventions that organizations follow to structure their hosts. Some organizations might use *{domain}/api*
while others might go for a *api.{name}.com*
structure.
Popular URL conventions
Either way, whatever convention you decide on, it is important to stick to it across your API suite. For example, if you want to use `/api`
convention, make sure all APIs have that in their hostnames defined in the OpenAPI.
server-has-api:
given: $.servers[*].url
then:
function: pattern
functionOptions:
# change match to notMatch if looking to never have /api
match: ^/api
message: Server must have /api
formats: oas3
severity: error
In case you are going with another convention, it would be a good idea to never have `/api
` in the path, in which case change the `Match
` to `notMatch
`
Versioning
There can be multiple versioning strategies for APIs. Some organizations might use URL versioning while others might go for path-level versioning.
Popular versioning strategies
URL Versioning
One common way to version is on the basepath level e.g. api.example.com/v1
. If you go for this versioning strategy, make sure that all basepaths have version numbers in them.
server-version:
given: $.servers[*].url
then:
function: pattern
functionOptions:
# use version[1-9] if looking for /version1 instead of /v1
# change match to notMatch if looking to never have version on host level
match: ^.*/v[1-9]
message: Server must have end with a version
formats: oas3
severity: error
Moreover, it’s a good idea to just have major versions within your base path. You can enforce that with the following configuration.
server-major-version-only:
given: $.servers[*].url
then:
function: pattern
functionOptions:
notMatch: .
message: Server should only have major versions
formats: oas3
severity: error
Path Versioning
If you are using path versioning, make sure that all paths have version numbers in them.
path-casing:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
# For underscore_case: ^/([a-z0-9]+(_[a-z0-9]+)*)?(/[a-z0-9]+(_[a-z0-9]+)*|/{.+})*$
# For camelCase case: ^/([a-z][a-zA-Z0-9]+)?(/[a-z][a-zA-Z0-9]+|/{[a-z][a-zA-Z0-9]+})*$
match: ^/([a-z0-9]+(-[a-z0-9]+)*)?(/[a-z0-9]+(-[a-z0-9]+)*|/{.+})*$
message: Paths must be kebab-case
severity: error
Path
Must be kebab-case
Paths are usually one of the following across popular APIs: kebab-case, underscore_case, or camelCase. It is advisable to use kebab-case, as It typically looks clearer and hence more user-friendly than using underscores (_).
Popular URL casing conventions
Again irrespective of the casing you choose it’s important to stay consistent.
In general, it’s usually preferred to use plural nouns for resources unless a plural doesn’t exist for the noun.
resource-names-plural:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
match: ^((/vd+)*((/[w+-]*s)(/{.*})*)*)$
message: Resource names should generally be plural
# Don’t create this as an error, but rather a warning or info level rule,
# considering there are exceptions for singleton resources.
severity: warn
Must not have a trailing slash
*/users*
and */users/*
are considered to be separate paths but it is bad practice for them to differ based only on a trailing slash. This can cause confusion among users of your API.
It is usually preferred to not have a trailing slash.
Again, either way, be consistent by creating a rule.
paths-no-trailing-slash:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
notMatch: /$
message: Paths must not end with a trailing slash
severity: error
Don’t use file extensions in Paths
The use of file extensions is simply seen as unnecessary in URLs. This is because it can cause some issues for the end-users if you change the file type of the results. Hence, it might add unnecessary complexity that you would like to avoid.
paths-no-trailing-slash:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
# Feel free to add other extensions to this list
notMatch: (JSON|json|XML|xml)
message: Paths must not have file extensions
severity: error
Don’t add HTTP methods to paths
When you design a REST API, you don’t usually need to mention terms like `get
`, `delete
` and so on in your `paths
`, because this information is conveyed by the HTTP method.
paths-no-trailing-slash:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
# Feel free to add other verbs to this list
notMatch: (GET|PUT|POST|DELETE|LIST|CREATE|REMOVE)
message: Paths must not have HTTP verbs in them
severity: error
paths-version-number:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
# Change match to notMatch if you're not looking for versions on the path level
match: (/v[1-9]|/version[1-9])
message: Paths must have a version in them
severity: error
Avoid Special Characters
URLs can only be sent and received using the ASCII character set, your API URLs should contain only ASCII characters. However, that does not mean you can just use all the characters. Special characters like %20 should not be used in paths.
paths-avoid-special-characters:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
# Change match to notMatch if you're not looking for versions on the path level
notMatch: ^[$&+,;=?@]*$
message: Avoid using special characters in paths
severity: warn
Use Nouns for Resource Names
Using nouns for resource names leads to easier-to-use APIs. This can be done via custom functions in Spectral which we’ll cover in another article in this series.
Parameters
Don’t include query params as part of the URI
Query parameters in OpenAPI are defined separately. Adding them to the path itself should be avoided.
paths-no-query-params:
given: $.paths
then:
# Targeting the key of paths object i.e. /products/{productId}
field: @key
function: pattern
functionOptions:
notMatch: ?
message: Paths should not have query parameters in them. They should be defined separately in the OpenAPI.
severity: warn
Path Parameter Casing
Path parameters are usually underscore_case or camelCase.
Popular path parameter casing conventions
path-parameters-camelcase:
given: $..parameters[?(@.in == 'path')].name
then:
function: casing
functionOptions:
type: camel
disallowDigits: true
message: Path parameters should be camelCase and not contain digits
severity: error
Query Parameter Casing
Query parameters are usually underscore_case or camelCase.
Popular query parameter casing conventions
query-parameters-camelcase:
given: $..parameters[?(@.in == 'query')].name
then:
function: casing
functionOptions:
type: camel
disallowDigits: true
message: Query parameters should be camelCase and not contain digits
severity: error
It is usually a good idea to have both query and path parameters follow the same conventions.
Path Parameters should not be defined on the operation level
Path parameters should be defined on the path level instead of the operation level.
path-parameters-camelcase:
given: $.paths[*][*].parameters[?(@.in == 'path')]
then:
function: falsy
message: Path parameters should be defined on the path level instead of the operation level.
severity: warn
Mandatory Paths
Always define a status path
APIs MUST have a status path defined (/status
), to easily get started with an API and ping for status updates.
status-path-defined:
given: $.paths[*][*]
then:
function: truthy
message: APIs MUST have a status path defined (`/status`)
severity: warn
With not much standardization and so many conventions to worry about when building REST APIs, a core set of guidelines that everybody agrees on is necessary. Convert them into style guide rules that can then be enforced as part of the design and governance process. A combination of these rules helps you create consistent REST API URLs, which leads to an enhanced developer experience. After all, over 50% of organizations use or plan on using internal style guidelines this year, and standardization is the #1 challenge teams are looking to overcome this year, according to the State of API report (2022). This is simply another way you can implement consistency and standardization in your own API program!
Get started with these rules in this sample project.
For more information on style guides, check out our feature blog or our open-source linting tool, Spectral. If you have other ideas on URL conventions and rules, submit an idea on our product roadmap.