Mobile applications require specialized API design considerations that differ from traditional web services. The constraints of mobile devices—including intermittent connectivity, limited bandwidth, battery life, and varying screen sizes—demand thoughtful API architecture.
Key principles for mobile APIs:
// Good resource naming example
GET /users/{id}/preferences
POST /notifications/{id}/read
Avoid RPC-style endpoints in favor of RESTful resources. Each endpoint should represent a specific resource with clear relationships.
{
"data": {
"id": "usr_123",
"name": "Jane Doe",
"avatar": {
"small": "https://cdn.example.com/avatars/small/123.jpg",
"medium": "https://cdn.example.com/avatars/medium/123.jpg"
}
},
"meta": {
"cache_ttl": 3600,
"api_version": "2025-01"
}
}
Include multiple image sizes for responsive mobile displays and provide metadata about caching and API versioning.
GET /posts?fields=id,title,preview&limit=20&offset=40
{
"items": [...],
"pagination": {
"total": 120,
"limit": 20,
"offset": 40,
"has_more": true
}
}
Implement cursor-based pagination for better performance with large datasets:
GET /posts?limit=20&after=cursor123
Always enable gzip/brotli compression. For JSON responses, consider minification in production:
# Nginx configuration
gzip on;
gzip_types application/json;
brotli on;
brotli_types application/json;
Allow clients to request only needed fields:
GET /users/me?fields=id,name,email
Implement ETag and Last-Modified headers:
GET /products/123
If-None-Match: "a1b2c3d4"
sequenceDiagram
Mobile App->>Auth Service: Initiate auth (PKCE)
Auth Service-->>Mobile App: Return auth code
Mobile App->>Backend: Exchange code for tokens
Backend->>Auth Service: Verify code
Auth Service-->>Backend: Return tokens
Backend-->>Mobile App: Return session tokens
Use OAuth 2.0 with PKCE (RFC 7636) for mobile applications:
// iOS PKCE example
let verifier = generateCodeVerifier()
let challenge = generateCodeChallenge(verifier: verifier)
let authURL = URL(string: "https://auth.example.com?client_id=MOBILE_APP&code_challenge=\(challenge)&code_challenge_method=S256")!
Implement refresh token rotation (RFC 6819):
# Python refresh token example
def refresh_access_token(refresh_token):
new_tokens = auth_service.refresh(
refresh_token,
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET
)
# Invalidate old refresh token
revoke_refresh_token(refresh_token)
return {
"access_token": new_tokens.access_token,
"refresh_token": new_tokens.refresh_token,
"expires_in": new_tokens.expires_in
}
GET /messages?since=2025-01-01T00:00:00Z
{
"updates": [...],
"deletes": ["msg_123", "msg_456"],
"current_server_time": "2025-01-02T12:00:00Z"
}
Implement Last-Write-Wins or operational transformation:
{
"error": {
"code": "conflict",
"message": "Version mismatch",
"server_version": 5,
"client_version": 4,
"server_data": {
"title": "Server Title",
"content": "Merged Content"
}
}
}
// Go push notification example
func sendPushNotification(userID string, payload PushPayload) error {
devices, err := db.GetUserDevices(userID)
if err != nil {
return err
}
for _, device := range devices {
switch device.Platform {
case "ios":
sendAPNS(device.Token, payload)
case "android":
sendFCM(device.Token, payload)
}
}
return nil
}
{
"aps": {
"alert": {
"title": "New Message",
"body": "You have a new message from John"
},
"content-available": 1
},
"data": {
"type": "message",
"id": "msg_123",
"thread_id": "thread_456"
}
}
query GetUserProfile {
user(id: "123") {
id
name
avatar(size: [64, 128]) {
small: url(size: 64)
medium: url(size: 128)
}
friends(first: 10) {
edges {
node {
id
name
}
}
}
}
}
# Query registration
curl -X POST https://api.example.com/graphql/persisted \
-H "Authorization: Bearer token123" \
-d '{
"id": "userProfileQuery",
"query": "query GetUserProfile($id: ID!) { user(id: $id) { id name }}"
}'
GET /graphql?queryId=userProfileQuery&variables={"id":"123"}
{
"error": {
"code": "invalid_auth",
"message": "Authentication token expired",
"details": {
"expired_at": "2025-01-01T00:00:00Z"
},
"links": {
"refresh_token": "/auth/refresh"
}
}
}
// Android retry with exponential backoff
fun <T> withRetry(block: () -> T): T {
var currentDelay = 1000L
val maxDelay = 30000L
while (true) {
try {
return block()
} catch (e: IOException) {
if (currentDelay >= maxDelay) throw e
Thread.sleep(currentDelay)
currentDelay = (currentDelay * 2).coerceAtMost(maxDelay)
}
}
}
# Flask middleware example
@app.after_request
def log_request(response):
metrics.timing(
"api.request_time",
(datetime.now() - request.start_time).total_seconds(),
tags={
"endpoint": request.endpoint,
"method": request.method,
"status": response.status_code
}
)
return response
// iOS network monitoring
class APIMonitor: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
return request.url?.host == "api.example.com"
}
override func startLoading() {
let start = Date()
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let duration = Date().timeIntervalSince(start)
Analytics.trackAPIRequest(
path: request.url?.path ?? "",
duration: duration,
statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0
)
// ... handle response
}
task.resume()
}
}
The mobile landscape continues to evolve, so regularly revisit your API design to incorporate new best practices and technologies as they emerge.