Welcome to Sparky

Switch to English

Sparky é um pacote Dart para construção de APIs REST de forma simples, com suporte a WebSocket, autenticação JWT, CORS, rotas dinâmicas e muito mais.

Features

  • Rotas dinâmicas com path parameters (:id)
  • Agrupamento de rotas com prefixo (RouteGroup)
  • Cache automático nas rotas (diferenciado por método HTTP)
  • Suporte a CORS configurável
  • Sistema de logs (console, arquivo ou ambos)
  • Suporte a WebSocket
  • Autenticação JWT com expiração
  • Pipeline antes e depois do middleware principal
  • Serialização automática de Map/List para JSON
  • Headers customizados na Response
  • Graceful shutdown
  • Parsing de JSON body, form-data e URL-encoded

Como Usar

Criando uma rota simples

import 'dart:io';
import 'package:sparky/sparky.dart';

void main() {
  final route1 = RouteHttp.get('/hello', middleware: (request) async {
    return const Response.ok(body: 'Olá mundo');
  });

  Sparky.server(routes: [route1]);
}

Rotas dinâmicas com path parameters

Defina segmentos dinâmicos com :param e acesse via request.pathParams.

final userRoute = RouteHttp.get('/users/:id', middleware: (request) async {
  final userId = request.pathParams['id'];
  return Response.ok(body: {'userId': userId, 'name': 'User $userId'});
});

final itemRoute = RouteHttp.get('/items/:category/:itemId', middleware: (request) async {
  return Response.ok(body: {
    'category': request.pathParams['category'],
    'itemId': request.pathParams['itemId'],
  });
});

Agrupamento de rotas (RouteGroup)

Agrupe rotas sob um prefixo comum e use flatten() para expandir.

final apiRoutes = RouteGroup('/api/v1', routes: [
  RouteHttp.get('/users', middleware: (r) async => const Response.ok(body: {'users': []})),
  RouteHttp.get('/products', middleware: (r) async => const Response.ok(body: {'products': []})),
]);

Sparky.server(routes: [
  ...apiRoutes.flatten(), // gera /api/v1/users e /api/v1/products
]);

Criando uma rota a partir de uma classe

final class RouteTest extends Route {
  RouteTest()
      : super('/test', middleware: (request) async {
          return const Response.ok(body: 'test');
        }, acceptedMethods: [
          AcceptedMethods.get,
          AcceptedMethods.post,
        ]);
}

final class RouteSocket extends Route {
  RouteSocket()
      : super('/socket', middlewareWebSocket: (WebSocket webSocket) async {
          webSocket.listen(print, onDone: () {
            webSocket.close();
          });
        });
}

void main() {
  Sparky.server(routes: [RouteTest(), RouteSocket()]);
}

Serialização automática para JSON

O body da Response aceita String, Map ou List. Valores não-String são serializados automaticamente.

final route = RouteHttp.get('/data', middleware: (request) async {
  return const Response.ok(body: {'message': 'hello', 'items': [1, 2, 3]});
});

Parsing de body (JSON, form-data, URL-encoded)

final route = RouteHttp.post('/submit', middleware: (request) async {
  // JSON body (application/json)
  final json = await request.getJsonBody();

  // URL-encoded (application/x-www-form-urlencoded)
  final form = await request.getFormData();

  // Multipart form-data
  final multipart = await request.getBodyParams();

  return Response.ok(body: {'received': json});
});

Headers customizados na Response

final route = RouteHttp.get('/download', middleware: (request) async {
  return const Response.ok(
    body: 'conteúdo',
    headers: {
      'X-Custom-Header': 'valor',
      'Cache-Control': 'no-cache',
    },
  );
});

Como personalizar o ip e porta

Sparky.server(
  routes: [...],
  ip: '0.0.0.0',
  port: 8080,
);

Como criar pipeline

Você pode adicionar N middlewares nas pipelines.

Sparky.server(
  routes: [...],
  pipelineBefore: Pipeline()
    ..add((request) async {
      // Retorne null para continuar ou uma Response para interromper
      return null;
    }),
  pipelineAfter: Pipeline()
    ..add((request) async {
      print('Executado após a rota');
      return null;
    }),
);

Suporte a CORS

const cors = CorsConfig(
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
);
// Ou use CorsConfig.permissive() para desenvolvimento

Sparky.server(
  routes: [...],
  pipelineBefore: Pipeline()
    ..add(cors.createMiddleware()),
);

Sistema de logs

Por padrão mostra e salva logs. Você pode configurar o tipo, o modo e o caminho do arquivo.

Sparky.server(
  routes: [...],
  logConfig: LogConfig.showAndWriteLogs,
  logType: LogType.all,
  logFilePath: 'server.log', // padrão: 'logs.txt'
);

Como usar WebSockets

final websocket = RouteWebSocket(
  '/ws',
  middlewareWebSocket: (WebSocket socket) async {
    socket.add('Hello World');
    socket.listen(
      print,
      onDone: () => socket.close(),
    );
  },
);

Sparky.server(routes: [websocket]);

Autenticação JWT com expiração

const authJwt = AuthJwt(secretKey: 'minha-chave-secreta');

final login = RouteHttp.post('/login', middleware: (request) async {
  final data = await request.getJsonBody();

  final token = authJwt.generateToken(
    {'username': data['user']},
    expiresIn: const Duration(hours: 2),
  );

  return Response.ok(body: {'token': token});
});

Sparky.server(
  routes: [login],
  pipelineBefore: Pipeline()
    ..add((request) async {
      if (request.uri.path == '/login') return null;

      final token = request.headers.value('Authorization');
      if (token != null && authJwt.verifyToken(token)) {
        final payload = authJwt.decodePayload(token);
        print('Usuário: ${payload?['username']}');
        return null;
      }

      return const Response.unauthorized(
        body: {'error': 'Token ausente ou inválido'},
      );
    }),
);

Cache de rotas

O cache é automático e diferenciado por método HTTP. Após a primeira execução, a rota retorna a resposta em cache. Chame onUpdate() para invalidar o cache.

final random = RouteHttp.get('/random', middleware: (request) async {
  final value = Random().nextInt(100);
  return Response.ok(body: {'value': value});
});

Sparky.server(
  routes: [random],
  pipelineBefore: Pipeline()
    ..add((request) async {
      random.onUpdate(); // invalida o cache, executa o código da rota
      return null;
    }),
);

Graceful shutdown

final server = Sparky.server(routes: [...]);
await server.ready; // aguarda o servidor estar pronto

// Quando quiser parar:
await server.close();

Como compilar para máxima performance

dart compile exe main.dart

Contribuições

O projeto é totalmente open source e contribuições são muito bem-vindas.

Libraries

sparky
@author viniciusddrft Support for doing something awesome. More dartdocs go here.