sparky 2.1.0 copy "sparky: ^2.1.0" to clipboard
sparky: ^2.1.0 copied to clipboard

Sparky é pacote que ajuda na construção de apis rest de forma simples com suporte a websocket a autenticação jwt.

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)
  • Guards por rota e por grupo
  • Cache automático nas rotas (diferenciado por método HTTP, com TTL e limite de entradas)
  • Suporte a CORS configurável (multi-origin, credentials, Vary: Origin)
  • Sistema de logs (console, arquivo ou ambos)
  • Suporte a WebSocket
  • Autenticação JWT com expiração (HS256, sem padding base64url, validação de algoritmo)
  • 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
  • Limite de tamanho de body (maxBodySize)
  • Timeout por request (requestTimeout)
  • Compressão gzip para responses normais e streams (enableGzip, gzipMinLength)
  • Rate limiting pronto para uso
  • Servir arquivos estáticos com StaticFiles (ETag, Last-Modified, 304)
  • Helpers de content negotiation e cookies
  • Validação de request body (Validator)
  • HTTPS/TLS nativo via SecurityContext

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',
    },
  );
});

Validação de request body #

final schema = Validator({
  'name': [isRequired, isString, minLength(3)],
  'email': [isRequired, isString, isEmail],
  'age': [isRequired, isNum, min(18)],
});

RouteHttp.post('/register', middleware: (request) async {
  final body = await request.getJsonBody();
  final errors = schema.validate(body);
  if (errors.isNotEmpty) {
    return Response.badRequest(body: {'errors': errors});
  }
  return Response.created(body: {'ok': true});
});

Guards por rota #

Guards são middlewares que rodam antes do handler da rota. Se qualquer guard retornar uma Response, a rota não executa.

Future<Response?> authGuard(HttpRequest request) async {
  final token = request.headers.value('Authorization');
  if (token != null && authJwt.verifyToken(token)) return null;
  return const Response.unauthorized(body: {'error': 'Não autorizado'});
}

final route = RouteHttp.get('/admin',
  middleware: (r) async => const Response.ok(body: {'admin': true}),
  guards: [authGuard],
);

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 #

// Múltiplas origins — o middleware reflete a origin do request se permitida
const cors = CorsConfig(
  allowOrigins: ['https://meuapp.com', 'https://admin.meuapp.com'],
  allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
);

// Com credentials (allowOrigins: ['*'] reflete a origin do request ao invés de *)
const corsWithCreds = CorsConfig(allowCredentials: true);

// 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;
    }),
);

Você também pode configurar TTL e limite máximo de entradas:

Sparky.server(
  routes: [...],
  cacheTtl: const Duration(seconds: 30),
  cacheMaxEntries: 500,
);

Limite de body e timeout #

Sparky.server(
  routes: [...],
  maxBodySize: 10 * 1024 * 1024, // 10 MB
  requestTimeout: const Duration(seconds: 10),
);

Servir arquivos estáticos #

Sparky.server(
  routes: [...],
  pipelineBefore: Pipeline()
    ..add(
      StaticFiles(
        urlPath: '/public',
        directory: './static',
        maxFileSize: 5 * 1024 * 1024, // opcional
      ).createMiddleware(),
    ),
);

Gzip #

Sparky.server(
  routes: [...],
  enableGzip: true,
  gzipMinLength: 1024,
);

Rate limit #

final limiter = RateLimiter(
  maxRequests: 100,
  window: const Duration(minutes: 1),
  trustProxyHeaders: true, // use apenas atrás de proxy confiável
);

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

Content negotiation #

RouteHttp.get('/data', middleware: (request) async {
  final preferred = request.preferredType(
    const ['application/json', 'text/html'],
  );
  if (preferred == 'text/html') {
    return Response.ok(body: '<h1>ok</h1>', contentType: ContentType.html);
  }
  return Response.ok(body: {'ok': true}, contentType: ContentType.json);
});

Cookies #

RouteHttp.get('/set-cookie', middleware: (request) async {
  final cookie = Cookie('session', 'token')
    ..httpOnly = true
    ..secure = true;
  return Response.ok(body: {'ok': true}, cookies: [cookie]);
});

HTTPS/TLS #

final context = SecurityContext()
  ..useCertificateChain('cert.pem')
  ..usePrivateKey('key.pem');

Sparky.server(
  routes: [...],
  securityContext: context,
);

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.

13
likes
150
points
251
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Sparky é pacote que ajuda na construção de apis rest de forma simples com suporte a websocket a autenticação jwt.

Repository (GitHub)
View/report issues

Topics

#sparky #server #api #backend

License

unknown (license)

Dependencies

crypto

More

Packages that depend on sparky