Skip to content

FabianVarela/dart_frog_backend_demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dart Frog Backend Demo

style: very good analysis License: MIT Powered by Dart Frog

Backend API application built with Dart Frog, featuring RESTful endpoints, GraphQL support, WebSocket, and real-time communication. Demonstrates CRUD operations, GraphQL queries, middleware implementation, and state management with broadcast_bloc.

Prerequisites

Before getting started, make sure you have the following installed:

  • Dart SDK: >=3.11.0 <4.0.0
  • Dart Frog CLI: Latest version
  • IDE: VSCode or IntelliJ IDEA with Dart extensions

Initial Setup

1. Install Dart Frog CLI

If you haven't installed Dart Frog CLI yet, install it globally:

dart pub global activate dart_frog_cli

Verify installation:

dart_frog --version

2. Clone the repository

git clone <repository-url>
cd dart_frog_backend_demo

3. Install dependencies

dart pub get

4. Generate code

This project uses code generation for JSON serialization:

dart run build_runner build --delete-conflicting-outputs

Development

Run the development server

Start the Dart Frog development server with hot reload:

dart_frog dev

The server will start at http://localhost:8080 by default.

Custom port

To run on a different port:

dart_frog dev --port 3000

Watch for changes

The dev server automatically reloads on file changes. No need to restart!

Production

Build for production

Build an optimized production server:

dart_frog build

This creates a compiled executable in build/.

Run production build

dart build/bin/server.dart

Production with custom configuration

# Custom port
dart build/bin/server.dart --port 8080

# Custom host
dart build/bin/server.dart --host 0.0.0.0

Project Structure

dart_frog_backend_demo/
├── routes/                             # API route handlers
│   ├── _middleware.dart                # Global middleware
│   ├── index.dart                      # GET / - Root endpoint
│   ├── ws.dart                         # WebSocket endpoint
│   ├── graphql.dart                    # POST /graphql - GraphQL endpoint
│   ├── graphql_ws.dart                 # WS /graphql_ws - GraphQL subscriptions
│   └── json/                           # JSON CRUD endpoints
│       ├── index.dart                  # GET/POST /json
│       └── [id].dart                   # GET/PUT/DELETE /json/:id
├── lib/                                # Shared business logic
│   ├── graphql/                        # GraphQL configuration
│   │   ├── graph_ql_schemas.dart       # GraphQL schema (queries, mutations, subscriptions)
│   │   └── graphql_subscriptions.dart  # Subscription event streams
│   └── model/                          # Data models
│       ├── user/                       # User model
│       │   ├── user_model.dart         # User data class
│       │   └── schema/                 # User GraphQL schema
│       │       └── user_schema.dart    # User type definition
│       └── address/                    # Address model
│           └── address_model.dart      # Address data class
├── test/                               # Unit and integration tests
│   ├── routes/                         # Route handler tests
│   └── models/                         # Model tests
├── public/                             # Static files
├── .dart_frog/                         # Generated Dart Frog files (don't edit)
├── main.dart                           # Server entry point
├── pubspec.yaml                        # Dependencies
└── analysis_options.yaml               # Linter rules

API Endpoints

Root Endpoint

GET /

Welcome endpoint returning server information.

curl http://localhost:8080/

Response:

{
  "message": "Welcome to Dart Frog!",
  "version": "1.0.0"
}

JSON CRUD Endpoints

These endpoints demonstrate RESTful operations using the UserModel.

UserModel Structure:

Field Type Description
name String User's name (required in request)
age Int User's age (required in request)
serverMessage String Message from server (auto-generated)
address Address User's address (auto-generated)

GET /json

Get all users.

curl http://localhost:8080/json

Response:

[
  {
    "name": "Dash",
    "age": 42,
    "serverMessage": "Welcome to Not The Dart Side!",
    "address": {
      "street": "Bogota",
      "number": 2503,
      "zipCode": 110721
    }
  }
]

POST /json

Create a new user.

curl -X POST http://localhost:8080/json \
  -H "Content-Type: application/json" \
  -d '{"name": "Fabian V.", "age": 30}'

Request Body:

{
  "name": "Fabian V.",
  "age": 30
}

Response (201 Created):

{
  "message": "ok",
  "body": {
    "name": "Fabian V.",
    "age": 30,
    "serverMessage": "Welcome to Not The Dart Side!",
    "address": {
      "street": "Bogota",
      "number": 2503,
      "zipCode": 110721
    }
  }
}

GET /json/:id

Get a specific user by ID.

curl http://localhost:8080/json/1

Response:

{
  "name": "Dash",
  "age": 42,
  "serverMessage": "Welcome to Not The Dart Side!",
  "address": {
    "street": "Bogota",
    "number": 2503,
    "zipCode": 110721
  }
}

PUT /json/:id

Update an existing user.

curl -X PUT http://localhost:8080/json/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Dash Updated", "age": 43}'

Request Body:

{
  "name": "Dash Updated",
  "age": 43
}

Response:

{
  "message": "Actualizado el id 1",
  "body": {
    "name": "Dash Updated",
    "age": 43,
    "serverMessage": "Welcome to the new The Dart Side!",
    "address": {
      "street": "Bogota",
      "number": 2503,
      "zipCode": 110721
    }
  }
}

DELETE /json/:id

Delete a user.

curl -X DELETE http://localhost:8080/json/1

Response (204 No Content):

(empty response body)

WebSocket Endpoint

WS /ws

WebSocket endpoint for real-time bidirectional communication.

JavaScript Client Example:

const ws = new WebSocket('ws://localhost:8080/ws');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ type: 'ping' }));
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);
};

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
};

ws.onclose = () => {
  console.log('Disconnected');
};

Dart Client Example:

import 'package:web_socket_channel/web_socket_channel.dart';

final channel = WebSocketChannel.connect(
  Uri.parse('ws://localhost:8080/ws'),
);

channel.stream.listen((message) {
  print('Received: $message');
});

channel.sink.add('{"type": "ping"}');

GraphQL Endpoint

POST /graphql

GraphQL endpoint for querying data with flexible queries.

Query - Get all users:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ users { name age serverMessage address { street number zipCode } } }"}'

Response:

{
  "data": {
    "users": [
      {
        "name": "Dash",
        "age": 42,
        "serverMessage": "Hello from server",
        "address": {
          "street": "Main St",
          "number": 123,
          "zipCode": 12345
        }
      }
    ]
  }
}

Query - Find user by ID:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ findUser(id: 1) { name age address { street number } } }"}'

Response:

{
  "data": {
    "findUser": {
      "name": "Dash",
      "age": 42,
      "address": {
        "street": "Main St",
        "number": 123
      }
    }
  }
}

Available Queries:

Query Arguments Returns Description
users - [User] Get all users
findUser id: Int User Find a user by ID

Available Mutations:

Mutation Arguments Returns Description
createUser name: String!, age: Int! User Create a new user
updateUser id: Int!, name: String, age: Int User Update an existing user
deleteUser id: Int! Boolean Delete a user by ID

Mutation - Create user:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "mutation { createUser(name: \"John\", age: 25) { name age serverMessage } }"}'

Response:

{
  "data": {
    "createUser": {
      "name": "John",
      "age": 25,
      "serverMessage": "User created successfully!"
    }
  }
}

Mutation - Update user:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "mutation { updateUser(id: 1, name: \"Jane\", age: 30) { name age serverMessage } }"}'

Mutation - Delete user:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "mutation { deleteUser(id: 1) }"}'

Mutation with variables:

curl -X POST http://localhost:8080/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateUser($name: String!, $age: Int!) { createUser(name: $name, age: $age) { name age } }",
    "variables": { "name": "Alice", "age": 28 }
  }'

User Type:

Field Type Description
name String User's name
age Int User's age
serverMessage String Message from server
address Address User's address

Address Type:

Field Type Description
street String Street name
number Int Street number
zipCode Int ZIP code

GraphQL Subscriptions (WebSocket)

WS /graphql_ws

WebSocket endpoint for GraphQL subscriptions with real-time updates.

Available Subscriptions:

Subscription Arguments Returns Description
onUserCreated - User Emits when a user is created via mutation
onUserUpdated - User Emits when a user is updated via mutation
onUserDeleted - Int Emits user ID when deleted via mutation
countdown from: Int! Int Countdown from a number (1 per second)

JavaScript Client Example:

const ws = new WebSocket('ws://localhost:8080/graphql_ws');

// Initialize connection
ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'connection_init' }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'connection_ack') {
    // Subscribe to new users
    ws.send(JSON.stringify({
      id: '1',
      type: 'subscribe',
      payload: {
        query: `subscription { onUserCreated { name age serverMessage } }`
      }
    }));

    // Subscribe to countdown
    ws.send(JSON.stringify({
      id: '2',
      type: 'subscribe',
      payload: {
        query: `subscription { countdown(from: 5) }`
      }
    }));
  }

  if (data.type === 'next') {
    console.log('Received:', data.payload.data);
  }

  if (data.type === 'complete') {
    console.log('Subscription completed:', data.id);
  }
};

// Stop a subscription
ws.send(JSON.stringify({ id: '1', type: 'stop' }));

Dart Client Example:

import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';

final channel = WebSocketChannel.connect(
  Uri.parse('ws://localhost:8080/graphql_ws'),
);

// Initialize connection
channel.sink.add(jsonEncode({'type': 'connection_init'}));

// Listen for messages
channel.stream.listen((message) {
  final data = jsonDecode(message as String) as Map<String, dynamic>;

  if (data['type'] == 'connection_ack') {
    // Subscribe to user created events
    channel.sink.add(jsonEncode({
      'id': '1',
      'type': 'subscribe',
      'payload': {
        'query': 'subscription { onUserCreated { name age } }',
      },
    }));
  }

  if (data['type'] == 'next') {
    print('Received: ${data['payload']}');
  }
});

Testing with Postman:

  1. Create a WebSocket request:

    • Click NewWebSocket
    • Enter URL: ws://localhost:8080/graphql_ws
    • Click Connect
  2. Initialize connection:

    {"type": "connection_init"}

    You'll receive: {"type": "connection_ack"}

  3. Subscribe to events:

    {
      "id": "1",
      "type": "subscribe",
      "payload": {
        "query": "subscription { onUserCreated { name age serverMessage } }"
      }
    }
  4. Test the subscription: In another Postman tab, trigger the mutation:

    POST http://localhost:8080/graphql
    Content-Type: application/json
    
    {"query": "mutation { createUser(name: \"Test\", age: 25) { name } }"}
    

    You'll see the event in the WebSocket connection.

  5. Other subscription examples:

    // Subscribe to user updates
    {
      "id": "2",
      "type": "subscribe",
      "payload": {
        "query": "subscription { onUserUpdated { name age serverMessage } }"
      }
    }
    
    // Subscribe to countdown
    {
      "id": "3",
      "type": "subscribe",
      "payload": {
        "query": "subscription { countdown(from: 10) }"
      }
    }
  6. Stop a subscription:

    {"id": "1", "type": "stop"}

WebSocket Protocol Messages:

Type Direction Description
connection_init Client → Server Initialize WebSocket connection
connection_ack Server → Client Connection acknowledged
subscribe Client → Server Start a subscription
next Server → Client Subscription data event
complete Server → Client Subscription completed
stop Client → Server Stop a subscription
ping Client → Server Heartbeat ping
pong Server → Client Heartbeat response
error Server → Client Error message

Features

RESTful API

  • CRUD Operations: Complete Create, Read, Update, Delete functionality
  • JSON Serialization: Type-safe JSON handling with code generation
  • UUID Generation: Unique identifiers for resources
  • HTTP Methods: Support for GET, POST, PUT, DELETE
  • Request Validation: Input validation and error handling
  • Response Formatting: Consistent JSON response structure

GraphQL Support

  • Flexible Queries: Query exactly the data you need
  • Mutations: Create, update, and delete operations
  • Subscriptions: Real-time updates via WebSocket
  • Type System: Strongly typed schema with User and Address types
  • Nested Objects: Support for nested object queries (User -> Address)
  • Query Arguments: Support for query parameters (e.g., findUser(id: 1))
  • Schema Definition: Clean schema definition using graphql_schema2

WebSocket Support

  • Real-time Communication: Bidirectional communication with clients
  • Broadcast Messages: Send messages to all connected clients
  • Event Handling: Custom event types and handlers
  • Connection Management: Track connected clients
  • State Synchronization: Real-time state updates across clients

Middleware

  • Global Middleware: Applied to all routes via _middleware.dart
  • Request Logging: Log all incoming requests
  • CORS Headers: Cross-Origin Resource Sharing support
  • Error Handling: Centralized error handling
  • Request Timing: Track request duration
  • Custom Headers: Add custom response headers

State Management

  • broadcast_bloc: Reactive state management for WebSocket
  • In-Memory Storage: Fast in-memory data storage (development)
  • State Broadcasting: Broadcast state changes to WebSocket clients
  • Event Sourcing: Track all state changes

Developer Experience

  • Hot Reload: Instant server reload on code changes
  • Type Safety: Full Dart type safety
  • Code Generation: Automated JSON serialization
  • Testing Support: Built-in testing utilities
  • Linting: Strict code quality with very_good_analysis

Middleware

Global Middleware (_middleware.dart)

The global middleware handles:

  1. CORS Headers: Enable cross-origin requests
  2. Request Logging: Log method, path, and timestamp
  3. Error Handling: Catch and format errors
  4. Performance Monitoring: Track request duration

Example middleware implementation:

Handler middleware(Handler handler) {
  return handler
      .use(requestLogger())
      .use(provider<String>((_) => 'dependency'))
      .use(cors());
}

Custom Middleware

Create route-specific middleware by adding _middleware.dart in any route directory.

Testing

Run all tests

dart test

Run tests with coverage

dart test --coverage=coverage

Generate coverage report

# Install coverage tools
dart pub global activate coverage

# Generate LCOV report
dart pub global run coverage:format_coverage \
  --lcov \
  --in=coverage \
  --out=coverage/lcov.info \
  --report-on=lib

# Generate HTML report
genhtml coverage/lcov.info -o coverage/html

# Open in browser
open coverage/html/index.html

Run specific test file

dart test test/routes/json/index_test.dart

Code Quality

Run code analysis

The project uses very_good_analysis to maintain code quality:

dart analyze

Format code

dart format .

Fix formatting issues

dart fix --apply

Main Dependencies

Framework

  • dart_frog: Fast, minimalistic backend framework for Dart
  • dart_frog_web_socket: WebSocket support for Dart Frog

State Management

  • broadcast_bloc: BLoC pattern with broadcast capabilities

GraphQL

  • graphql_schema2: GraphQL schema definition
  • graphql_server2: GraphQL server implementation

Data Serialization

  • json_annotation: JSON serialization annotations
  • json_serializable: Code generation for JSON serialization

Utilities

  • uuid: Generate unique identifiers

Dev Dependencies

  • build_runner: Code generation runner
  • test: Dart testing framework
  • mocktail: Mocking library for tests
  • very_good_analysis: Strict lint rules

Architecture

Route-Based Architecture

Dart Frog uses a file-system based routing:

  • Files in routes/ directory automatically become endpoints
  • index.dart maps to directory path
  • [param].dart creates dynamic route parameters
  • _middleware.dart applies middleware to routes in the directory

Request Lifecycle

  1. Request Received: Incoming HTTP request
  2. Middleware Chain: Global and route-specific middleware
  3. Route Handler: Matched route handler executes
  4. Response: JSON or custom response returned
  5. Logging: Request logged and metrics collected

Dependency Injection

Dart Frog supports dependency injection via provider:

// In middleware
handler.use(provider<DatabaseService>((_) => DatabaseService()));

// In route
final database = context.read<DatabaseService>();

Deployment

Docker Deployment

Create a Dockerfile:

FROM dart:stable AS build

WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

COPY . .
RUN dart pub get --offline
RUN dart run build_runner build --delete-conflicting-outputs
RUN dart_frog build

FROM dart:stable-slim
COPY --from=build /app/build/bin/server /app/bin/
COPY --from=build /app/public /app/public

EXPOSE 8080
CMD ["/app/bin/server"]

Build and run:

docker build -t dart-frog-app .
docker run -p 8080:8080 dart-frog-app

Cloud Deployment

Google Cloud Run

# Build production server
dart_frog build

# Deploy to Cloud Run
gcloud run deploy dart-frog-app \
  --source . \
  --platform managed \
  --region us-central1 \
  --allow-unauthenticated

AWS Lambda

Use dart_frog_lambda package for AWS Lambda deployment.

Heroku

Create Procfile:

web: dart build/bin/server.dart --port $PORT

Deploy:

heroku create
git push heroku main

Environment Variables

Configuration

Use environment variables for configuration:

import 'dart:io';

final port = int.parse(Platform.environment['PORT'] ?? '8080');
final host = Platform.environment['HOST'] ?? 'localhost';
final databaseUrl = Platform.environment['DATABASE_URL'];

Local Development

Create .env file (don't commit):

PORT=8080
HOST=localhost
DATABASE_URL=postgresql://localhost/db
API_KEY=your_secret_key

Load with a package like dotenv:

import 'package:dotenv/dotenv.dart';

final env = DotEnv()..load();
final apiKey = env['API_KEY'];

Troubleshooting

Error: "dart_frog command not found"

Install Dart Frog CLI globally:

dart pub global activate dart_frog_cli

Add to PATH (if needed):

export PATH="$PATH":"$HOME/.pub-cache/bin"

Port already in use

Change the port:

dart_frog dev --port 3000

Or kill the process using port 8080:

# macOS/Linux
lsof -ti:8080 | xargs kill -9

# Windows
netstat -ano | findstr :8080
taskkill /PID <PID> /F

Hot reload not working

  1. Save the file explicitly
  2. Check for syntax errors
  3. Restart dev server:
    dart_frog dev

WebSocket connection fails

  • Verify WebSocket endpoint is running
  • Check CORS configuration
  • Use correct protocol (ws:// not http://)
  • Check firewall settings

Code generation fails

Clean and regenerate:

dart run build_runner clean
dart pub get
dart run build_runner build --delete-conflicting-outputs

Tests not running

Ensure test dependencies are installed:

dart pub get

Run with verbose output:

dart test --reporter=expanded

CORS errors

Add CORS middleware in _middleware.dart:

Handler middleware(Handler handler) {
  return handler.use(cors());
}

Middleware cors() {
  return (handler) {
    return (context) async {
      final response = await handler(context);
      return response.copyWith(
        headers: {
          ...response.headers,
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
          'Access-Control-Allow-Headers': 'Content-Type',
        },
      );
    };
  };
}

Production build fails

  1. Run code generation first
  2. Check for Dart analysis errors
  3. Verify all dependencies are compatible
dart analyze
dart run build_runner build --delete-conflicting-outputs
dart_frog build

License

This project is licensed under the MIT License - see the LICENSE for details.


Generated with Dart Frog 🐸

About

Backend created with dart and dart_frog library

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors