API Design That Defends Itself

By Davy Rogers

REST, GraphQL, gRPC patterns that shrink attack surface by design.

Principles

Minimal surface: Expose only what's needed. Every endpoint is attack surface.

Explicit over implicit: Access control visible, not buried in queries.

Fail closed: Unexpected = deny.

REST patterns

Resource-based URLs: Easier to apply consistent auth.

Filter response fields: Use DTOs. Never return raw DB objects.

Pagination: Always. Unbounded lists = data exfiltration. Max per_page server-side.

Rate limiting: Per endpoint, per user. Stricter for auth endpoints.

Consistent errors: Don't leak info. "Invalid credentials" not "user not found" vs "wrong password".

GraphQL

Disable introspection in production.

Depth and complexity limits: Without them, nested queries cause exponential DB work.

const server = new ApolloServer({
  introspection: process.env.NODE_ENV !== "production",
  validationRules: [depthLimit(5), createComplexityLimitRule(1000)],
});

Per-field authorisation: Single /graphql endpoint - auth must be at resolver level.

email: (parent, args, context) => {
  if (context.user.id !== parent.id && !context.user.isAdmin) return null;
  return parent.email;
}

gRPC

TLS always - even between internal services.

Authenticate via metadata - validate in interceptors.

Message size limits: grpc-go defaults to 4 MiB on receive. Lower it for endpoints that handle small payloads: grpc.MaxRecvMsgSize(1 * 1024 * 1024).

The takeaway

Minimal surface, explicit access control, fail closed. Resource-based URLs, filter responses, paginate, rate limit. GraphQL: disable introspection, limit depth, per-field auth. gRPC: TLS, metadata auth, size limits.

Want a professional to look at it?Get an AppSec Health Check.