Skip to main content

Getting Started

Installation and Setup

Prerequisites

Before diving into Hypermodern development, ensure you have:

  • Dart SDK 3.0.0 or later: Download from dart.dev
  • A code editor: VS Code with the Dart extension is recommended
  • Basic Dart knowledge: Familiarity with Dart syntax and concepts

Installing the Hypermodern CLI

The Hypermodern CLI is your primary tool for creating projects, generating code, and managing development workflows.

Install it globally:

dart pub global activate hypermodern_cli

Verify the installation:

hypermodern --version

You should see output similar to:

Hypermodern CLI v1.0.0

Setting Up Your Development Environment

For the best development experience, configure your editor:

VS Code Extensions:

  • Dart (official)
  • Flutter (if you plan to build Flutter clients)
  • JSON (for schema editing)

Editor Settings: Enable format on save and automatic imports for a smoother workflow.

Your First Hypermodern Project

Let's create a simple user management API to demonstrate Hypermodern's capabilities.

Creating the Project

hypermodern create user_api
cd user_api

This creates a new project with the following structure:

user_api/
├── schemas/
│   └── api.json          # API schema definitions
├── lib/
│   ├── generated/        # Auto-generated code
│   ├── models/          # Custom model extensions
│   └── main.dart        # Server entry point
├── client_example/
│   └── main.dart        # Example client usage
├── pubspec.yaml         # Dependencies
└── README.md           # Project documentation

Understanding the Project Structure

schemas/: Contains JSON schema files that define your API contract. This is where you'll spend most of your time defining models and endpoints.

lib/generated/: Auto-generated code based on your schemas. Never edit these files directly - they're regenerated whenever schemas change.

lib/models/: Custom extensions and business logic for your generated models.

client_example/: Example client code showing how to consume your API.

Examining the Default Schema

Open schemas/api.json to see the default schema:

{
  "models": {
    "user": {
      "id": "int64",
      "name": "string",
      "email": "string",
      "created_at": "datetime"
    }
  },
  "endpoints": {
    "get_user": {
      "method": "GET",
      "path": "/users/{id}",
      "request": {
        "id": "int64"
      },
      "response": "@user",
      "transports": ["http", "websocket", "tcp"]
    },
    "create_user": {
      "method": "POST", 
      "path": "/users",
      "request": {
        "name": "string",
        "email": "string"
      },
      "response": "@user",
      "transports": ["http", "websocket", "tcp"]
    }
  }
}

This schema defines:

  • A user model with id, name, email, and creation timestamp
  • Two endpoints: get_user and create_user
  • Support for all three transport protocols

Generating Code

Generate the type-safe client and server code:

hypermodern generate

This creates several files in lib/generated/:

  • models.dart: User model with serialization
  • client.dart: Type-safe client library
  • server.dart: Server endpoint stubs

Implementing Server Logic

Open lib/main.dart and implement the endpoint handlers:

import 'package:hypermodern_server/hypermodern_server.dart';
import 'generated/models.dart';
import 'generated/server.dart';

// In-memory storage for demo
final Map<int, User> users = {};
int nextId = 1;

void main() async {
  final server = HypermodernServer();
  
  // Register endpoint handlers
  server.registerEndpoint<GetUserRequest, User>(
    'get_user',
    (request) async {
      final user = users[request.id];
      if (user == null) {
        throw NotFoundException('User not found');
      }
      return user;
    },
  );
  
  server.registerEndpoint<CreateUserRequest, User>(
    'create_user',
    (request) async {
      final user = User(
        id: nextId++,
        name: request.name,
        email: request.email,
        createdAt: DateTime.now(),
      );
      users[user.id] = user;
      return user;
    },
  );
  
  // Start all protocol servers
  await server.listen(
    httpPort: 8080,
    wsPort: 8082,
    tcpPort: 8081,
  );
  
  print('🚀 Server running on:');
  print('   HTTP:      http://localhost:8080');
  print('   WebSocket: ws://localhost:8082');
  print('   TCP:       localhost:8081');
}

Running the Development Server

Start the server with hot reload:

hypermodern serve

The server will start and display:

🚀 Server running on:
   HTTP:      http://localhost:8080
   WebSocket: ws://localhost:8082
   TCP:       localhost:8081

👀 Watching for changes...

Basic Client-Server Communication

Testing with HTTP

You can test the HTTP endpoint using curl:

# Create a user
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'

# Get the user
curl http://localhost:8080/users/1

Using the Generated Client

Create a simple client in client_example/main.dart:

import 'package:hypermodern/hypermodern.dart';
import '../lib/generated/models.dart';
import '../lib/generated/client.dart';

void main() async {
  // Create client (automatically selects WebSocket)
  final client = HypermodernClient('ws://localhost:8082');
  
  try {
    // Connect to server
    await client.connect();
    print('✅ Connected to server');
    
    // Create a user
    final createRequest = CreateUserRequest(
      name: 'Bob',
      email: 'bob@example.com',
    );
    
    final newUser = await client.request<User>('create_user', createRequest);
    print('👤 Created user: ${newUser.name} (ID: ${newUser.id})');
    
    // Get the user back
    final getRequest = GetUserRequest(id: newUser.id);
    final fetchedUser = await client.request<User>('get_user', getRequest);
    print('📥 Fetched user: ${fetchedUser.name}');
    
  } catch (e) {
    print('❌ Error: $e');
  } finally {
    await client.disconnect();
    print('👋 Disconnected');
  }
}

Run the client:

cd client_example
dart run main.dart

You should see:

✅ Connected to server
👤 Created user: Bob (ID: 1)
📥 Fetched user: Bob
👋 Disconnected

Protocol Flexibility

The beauty of Hypermodern is that you can switch protocols without changing your client code:

// Use HTTP instead
final httpClient = HypermodernClient('http://localhost:8080');

// Use TCP for maximum performance
final tcpClient = HypermodernClient('tcp://localhost:8081');

// All use the same API!
final user = await client.request<User>('get_user', GetUserRequest(id: 1));

Understanding the Generated Code

Type-Safe Models

The generated User class includes:

class User {
  final int id;
  final String name;
  final String email;
  final DateTime createdAt;
  
  User({
    required this.id,
    required this.name,
    required this.email,
    required this.createdAt,
  });
  
  // Serialization methods
  Map<String, dynamic> toJson() => { /* ... */ };
  factory User.fromJson(Map<String, dynamic> json) => { /* ... */ };
  
  // Binary serialization
  Uint8List toBinary() => { /* ... */ };
  factory User.fromBinary(Uint8List data) => { /* ... */ };
}

Request/Response Types

For each endpoint, request and response types are generated:

class GetUserRequest {
  final int id;
  GetUserRequest({required this.id});
  // Serialization methods...
}

class CreateUserRequest {
  final String name;
  final String email;
  CreateUserRequest({required this.name, required this.email});
  // Serialization methods...
}

Client Methods

The generated client provides type-safe methods:

class ApiClient {
  Future<User> getUser(GetUserRequest request) async {
    return await _client.request<User>('get_user', request);
  }
  
  Future<User> createUser(CreateUserRequest request) async {
    return await _client.request<User>('create_user', request);
  }
}

Development Workflow

Schema-First Development

  1. Define your API in schemas/api.json
  2. Generate code with hypermodern generate
  3. Implement handlers in your server code
  4. Test with clients using the generated client library

Hot Reload

When using hypermodern serve, changes to your schema or server code trigger automatic regeneration and server restart. This makes iteration fast and seamless.

Adding New Endpoints

To add a new endpoint, update your schema:

{
  "endpoints": {
    "list_users": {
      "method": "GET",
      "path": "/users",
      "request": {},
      "response": {
        "users": ["@user"]
      },
      "transports": ["http", "websocket", "tcp"]
    }
  }
}

Run hypermodern generate and implement the handler:

server.registerEndpoint<ListUsersRequest, ListUsersResponse>(
  'list_users',
  (request) async {
    return ListUsersResponse(users: users.values.toList());
  },
);

What's Next

You now have a working Hypermodern application with multi-protocol support and type-safe communication. In the next chapter, we'll dive deeper into the core concepts that make this possible, including the transport protocols, unified routing system, and binary serialization.

You'll learn how Hypermodern achieves protocol transparency and why this approach is so powerful for modern application development.