How to Write APIs Without Writing Controllers

How to Write APIs Without Writing Controllers

When we usually talk about building APIs, the first thought that comes to mind is writing a controller. We define our routes, map them to methods, and then start coding the logic. That’s the traditional way.

But recently, I came across a different approach that changed how I look at API development — the API First approach.

Here’s how it works:

👉 Instead of writing the controller first, you define your API specification (for example, using OpenAPI/Swagger). This specification becomes the source of truth.

👉 From this spec, the framework generates the boilerplate code for you — including the controller.

👉 Along with the controller, something very interesting gets generated: a delegate interface. This is like a contract, where every API endpoint corresponds to a method in the delegate.

👉 As a developer, your job is not to write the controller anymore. Instead, you just implement this delegate interface. You write the actual business logic inside those methods.


⚙️ Example with Spring Boot and Maven

Spring Boot provides a Maven plugin that makes this super easy. Here’s how you can use it:

  1. Add the OpenAPI Generator Maven Plugin in your pom.xml:

<plugin>
  <groupId>org.openapitools</groupId>
  <artifactId>openapi-generator-maven-plugin</artifactId>
  <version>7.0.0</version>
  <executions>
    <execution>
      <goals>
        <goal>generate</goal>
      </goals>
      <configuration>
        <inputSpec>${project.basedir}/src/main/resources/openapi.yaml</inputSpec>
        <generatorName>spring</generatorName>
        <output>${project.build.directory}/generated-sources</output>
      </configuration>
    </execution>
  </executions>
</plugin>
        

  1. Place your openapi.yaml or openapi.json spec file in the resources folder.
  2. Run:

mvn clean install
        

This generates the controller and delegate interface in the target/generated-sources folder.


📄 Example of a Generated Class (from OpenAPI)

Let’s say your openapi.yaml defines a GET /users/{id} endpoint. The generator creates two key classes:

Generated Controller (UserApi.java)

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
@RestController
@RequestMapping("/api")
public interface UserApi {

    @GetMapping("/users/{id}")
    ResponseEntity<User> getUserById(@PathVariable("id") Long id);
}
        

Generated Delegate Interface (UserApiDelegate.java)

@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public interface UserApiDelegate {

    default ResponseEntity<User> getUserById(Long id) {
        // default implementation (can return 501 Not Implemented)
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }
}
        

Your job: implement the delegate.

@Service
public class UserApiDelegateImpl implements UserApiDelegate {

    @Override
    public ResponseEntity<User> getUserById(Long id) {
        User user = new User(id, "Rajjo");
        return ResponseEntity.ok(user);
    }
}
        

Notice how the heavy lifting (annotations, mappings, boilerplate) is already handled by the generated code. You only focus on logic.


🤝 Bonus: Mock APIs for Frontend Teams

One hidden superpower of this approach is how useful it is for mock APIs. Since everything starts from the OpenAPI spec, you can:

  • Generate the server stubs quickly.
  • Return mock data from the delegate (without real DB or services).
  • Share these endpoints with your frontend team, so they can start integration immediately.

For example:

@Override
public ResponseEntity<User> getUserById(Long id) {
    // Mock response for frontend testing
    User mockUser = new User(id, "Test User");
    return ResponseEntity.ok(mockUser);
}
        

This way, frontend teams don’t have to wait for backend development to finish. They can work in parallel, using the same API contract that will be used in production.


🔑 Why this excites me:

  • No repetitive boilerplate code.
  • Clear separation of concerns — spec defines what the API is, and delegate defines how it works.
  • Faster, cleaner, and more maintainable development.
  • Enables early collaboration by providing mock APIs to frontend teams.

✨ Have you worked with this API First approach (maybe with Spring Boot) before? How was your experience?

To view or add a comment, sign in

More articles by Rajat Singh

Others also viewed

Explore content categories