A performant, expressjs like server framework with a few gadgets that make life even easier.

Overview

Alfred

A performant, expressjs like server framework thats easy to use and has all the bits in one place.

Build Status

Quickstart:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/example', (req, res) => 'Hello world');

  await app.listen();
}

Index

Core principles

  • A minimum of dependencies,
  • A minimum of code and sticking close to dart core libraries - easy to maintain!
  • Ease of use
  • Predictable, well established semantics
  • 90%+ of everything you need all ready to go

Read about the background behind the project or why its different to shelf

Usage overview

If you have ever used expressjs before you should be right at home:

import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/text', (req, res) => 'Text response');

  app.get('/json', (req, res) => {'json_response': true});

  app.get('/jsonExpressStyle', (req, res) {
    res.json({'type': 'traditional_json_response'});
  });

  app.get('/file', (req, res) => File('test/files/image.jpg'));

  app.get('/html', (req, res) {
    res.headers.contentType = ContentType.html;
    return '<html><body><h1>Test HTML</h1></body></html>';
  });

  await app.listen(6565); //Listening on port 6565
}

It should do pretty much what you expect. Handling bodies though do need an "await":

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.post('/post-route', (req, res) async {
    final body = await req.body; //JSON body
    body != null; //true
  });

  await app.listen(); //Listening on port 3000
}

Internally dart provides a body parser, so no extra dependencies there.

The big difference you will see is the option to not call res.send or res.json etc - although you still can. Each route accepts a Future as response. Currently you can pass back the following and it will be sent appropriately:

Return Dart Type Returning REST type
List<dynamic> JSON
Map<String, Object?> JSON
Serializable object (Object.toJSON or Object.toJson) * see note JSON
String Plain Text
Stream<List<int>> Binary
List<int> Binary
File Binary, with mime type inferred by extension
Directory Serves static files

* If your object has a "toJSON" or "toJson" function, alfred will run it, and then return the result

If you want to return HTML, just set the content type to HTML like this:

import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/html', (req, res) {
    res.headers.contentType = ContentType.html;
    return '<html><body><h1>Title!</h1></body></html>';
  });

  await app.listen(); //Listening on port 3000
}

If you want to return a different type and have it handled automatically, you can extend Alfred with custom type handlers.

Quick start guide

If its all a bit overwhelming @iapicca put together a quick start guide which goes into a little more detail: https://medium.com/@iapicca/alfred-an-express-like-server-framework-written-in-dart-1661e8963db9

Routing & incoming requests

Routing follows a similar pattern to the more basic ExpressJS routes. While there is some regex matching, mostly just stick with the route name and param syntax from Express:

  • /path/to/:id/property etc

The Express syntax has been extended to support parameter patterns and types. To enforce parameter validation, a regular expression or a type specifier should be provided after the parameter name, using another : as a separator:

  • /path/to/:id:\d+/property will ensure "id" is a string consisting of decimal digits
  • /path/to/:id:[0-9a-f]+/property will ensure "id" is a string consisting of hexadecimal digits
  • /path/to/:word:[a-z]+/property will ensure "word" is a string consisting of letters only
  • /path/to/:id:uuid/property will ensure "id" is a string representing an UUID

Available type specifiers are:

  • int: a decimal integer
  • uint: a positive decimal integer
  • double: a double (decimal form); note that scientific notation is not supported
  • date: a UTC date in the form of "year/month/day"; note how this type "absorbs" multiple segments of the URI
  • timestamp: a UTC date expressed in number of milliseconds since Epoch
  • uuid: a string resembling a UUID (hexadecimal number formatted as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx); note that no effort is made to ensure this is a valid UUID
Type Specifier Regular Expression Dart type
int -?\d+ int
uint \d+ int
double -?\d+(?:\.\d+) double
date -?\d{1,6}/(?:0[1-9]|1[012])/(?:0[1-9]|[12][0-9]|3[01]) DateTime
timestamp -?\d+ DateTime
uuid [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} String

So for example:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();
  app.all('/example/:id:int/:name', (req, res) {
    req.params['id'] != null;
    req.params['id'] is int;
    req.params['name'] != null;
  });
  app.all('/example/:id/:name', (req, res) {
    req.params['id'] != null;
    req.params['name'] != null;
  });
  app.get('/blog/:date:date/:id:int', (req, res) {
    req.params['date'] != null;
    req.params['date'] is DateTime;
    req.params['id'] != null;
    req.params['id'] is int;
  });
  await app.listen();
}

You can also use a wildcard for a route, and provided another route hasn't already resolved the response it will be hit. So for example if you want to authenticate a whole section of an api youc can do this:

import 'dart:async';
import 'dart:io';

import 'package:alfred/alfred.dart';

FutureOr _authenticationMiddleware(HttpRequest req, HttpResponse res) async {
  res.statusCode = 401;
  await res.close();
}

void main() async {
  final app = Alfred();

  app.all('/resource*', (req, res) => _authenticationMiddleware);

  app.get('/resource', (req, res) {}); //Will not be hit
  app.post('/resource', (req, res) {}); //Will not be hit
  app.post('/resource/1', (req, res) {}); //Will not be hit

  await app.listen();
}

Route params

You can access any params for routes from the req.params object as below:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();
  app.all('/example/:id/:name', (req, res) {
    req.params['id'] != null;
    req.params['name'] != null;
  });
  await app.listen();
}

Query string variables

Querystring variables are exposed req.uri.queryParameters object in the request as below:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.post('/route', (req, res) async {
    /// Handle /route?qsvar=true
    final result = req.uri.queryParameters['qsvar'];
    result == 'true'; //true
  });

  await app.listen(); //Listening on port 3000
}

Body parsing

To access the body, simply call await req.body.

Alfred will interpret the body type from the content type headers and parse it appropriately. It handles url encoded, multipart & json bodies out of the box.

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.post('/post-route', (req, res) async {
    final body = await req.body; //JSON body
    body != null; //true
  });

  await app.listen(); //Listening on port 3000
}

File uploads

To upload a file the body parser will handle exposing the data you need. Its actually pretty easy just give it a go and set a breakpoint to see what the body parser spits back.

A working example of file uploads is below to get you started:

import 'dart:io';

import 'package:alfred/alfred.dart';

final _uploadDirectory = Directory('uploadedFiles');

Future<void> main() async {
  final app = Alfred();

  app.get('/files/*', (req, res) => _uploadDirectory);

  /// Example of handling a multipart/form-data file upload
  app.post(
      '/upload',
      (req, res) => (HttpRequest req, HttpResponse res) async {
            final body = await req.bodyAsJsonMap;

            // Create the upload directory if it doesn't exist
            if (await _uploadDirectory.exists() == false) {
              await _uploadDirectory.create();
            }

            // Get the uploaded file content
            final uploadedFile = (body['file'] as HttpBodyFileUpload);
            var fileBytes = (uploadedFile.content as List<int>);

            // Create the local file name and save the file
            await File('${_uploadDirectory.absolute}/${uploadedFile.filename}')
                .writeAsBytes(fileBytes);

            /// Return the path to the user
            ///
            /// The path is served from the /files route above
            return ({
              'path':
                  'https://${req.headers.host ?? ''}/files/${uploadedFile.filename}'
            });
          });

  await app.listen();
}

Middleware

You can specify a middleware for all routes by using wildcards:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();
  app.all('*', (req, res) {
    // Perform action
    req.headers.add('x-custom-header', "Alfred isn't bad");

    /// No need to call next as we don't send a response.
    /// Alfred will find the next matching route
  });

  app.get('/otherFunction', (req, res) {
    //Action performed next
    return {'message': 'complete'};
  });

  await app.listen();
}

Middleware declared this way will be executed in the order its added to the app.

You can also add middleware to a route, this is great to enforce authentication etc on an endpoint:

import 'dart:async';
import 'dart:io';

import 'package:alfred/alfred.dart';

FutureOr exampleMiddlware(HttpRequest req, HttpResponse res) {
  // Do work
  if (req.headers.value('Authorization') != 'apikey') {
    throw AlfredException(401, {'message': 'authentication failed'});
  }
}

void main() async {
  final app = Alfred();
  app.all('/example/:id/:name', (req, res) {}, middleware: [exampleMiddlware]);

  await app.listen(); //Listening on port 3000
}

What? No 'next'? how do I even?

OK, so the rules are simple. If a middleware resolves a http request, no future middleware gets executed.

So if you return an object from the middleware, you are preventing future middleware from executing.

If you return null it will yield to the next middleware or route.

** returning null is the equivalent of 'next' **

CORS

There is a cors middleware supplied for your convenience. Its also a great example of how to write a middleware for Alfred

import 'package:alfred/alfred.dart';
import 'package:alfred/src/middleware/cors.dart';

void main() async {
  final app = Alfred();

  // Warning: defaults to origin "*"
  app.all('*', cors(origin: 'myorigin.com'));

  await app.listen();
}

Responses

Alfred is super easy, generally you just return JSON, a file, a String or a Binary stream and you are all good.

The big difference from express is you will see is the option to not call res.send or res.json etc - although you still can. Each route accepts a Future as response. Currently you can pass back the following and it will be sent appropriately:

  • List<dynamic> - JSON
  • Map<String, Object?> - JSON
  • String - Plain text
  • Stream<List<int>> - Binary
  • List<int> - Binary
  • File - Binary, with mime type inferred by extension
  • Directory - Serves static files

Each type listed above has a Type Handler build in. You can create your own custom type handlers

Custom type handlers

Alfred has a pretty cool mechanism thanks to Dart's type system to automatically resolve a response based on the returned type from a route. These are called Type Handlers.

If you want to create custom type handlers, just add them to the type handler array in the app object. This is a bit advanced, and I expect it would be more for devs wanting to extend Alfred:

import 'package:alfred/alfred.dart';

class Chicken {
  String get response => 'I am a chicken';
}

void main() {
  final app = Alfred();

  app.typeHandlers.add(TypeHandler<Chicken>((req, res, Chicken val) async {
    res.write(val.response);
    await res.close();
  }));

  /// The app will now return the Chicken.response if you return one from a route

  app.get('/kfc', (req, res) => Chicken()); //I am a chicken;

  app.listen(); //Listening on 3000
}

Static Files, uploads and deleting

This one is super easy - just pass in a public path and a dart Directory object and Alfred does the rest.

import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  /// Note the wildcard (*) this is very important!!
  app.get('/public/*', (req, res) => Directory('test/files'));

  await app.listen();
}

You can also pass in a directory and a POST or PUT command and upload files to a local directory if you are using multipart/form encoding. Simply supply the field as file:

import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.post('/public', (req, res) => Directory('test/files'));

  await app.listen();
}

If you want to delete a file?

import 'dart:async';
import 'dart:io';

import 'package:alfred/alfred.dart';

FutureOr isAuthenticatedMiddleware(HttpRequest req, HttpResponse res) {
  if (req.headers.value('Authorization') != 'MYAPIKEY') {
    throw AlfredException(
        401, {'error': 'You are not authorized to perform this operation'});
  }
}

void main() async {
  final app = Alfred();

  /// Note the wildcard (*) this is very important!!
  ///
  /// You almost certainly want to protect this endpoint with some middleware
  /// to authenticate a user.
  app.delete('/public/*', (req, res) => Directory('test/files'),
      middleware: [isAuthenticatedMiddleware]);

  await app.listen();
}

Security? Build in a middleware function to authenticate a user etc.

File downloads

As mentioned above - if you want to return a file, simply return it from the route callback. However the browser will probably try to render it in browser, and not download it.

You can just set the right headers, but there is a handy little helper that will do it all for you.

See res.setDownload below.

import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/image/download', (req, res) {
    res.setDownload(filename: 'image.jpg');
    return File('test/files/image.jpg');
  });

  await app.listen(); //Listening on port 3000
}

Error handling

You can either set the status code on the response object yourself and send the data manually, or you can do this from any route:

app.get("/",(req, res) => throw AlfredException(400, {"message": "invalid request"}));

If any of the routes bubble an unhandled error, it will catch it and throw a 500 error.

If you want to handle the logic when a 500 error is thrown, you can add a custom handler when you instantiate the app. For example:

import 'dart:async';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred(onInternalError: errorHandler);
  await app.listen();
  app.get('/throwserror', (req, res) => throw Exception('generic exception'));
}

FutureOr errorHandler(HttpRequest req, HttpResponse res) {
  res.statusCode = 500;
  return {'message': 'error not handled'};
}

404 Handling

404 Handling works the same as 500 error handling (or uncaught error handling). There is a default behaviour, but if you want to override it, simply handle it in the app declaration.

import 'dart:async';
import 'dart:io';

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred(onNotFound: missingHandler);
  await app.listen();
}

FutureOr missingHandler(HttpRequest req, HttpResponse res) {
  res.statusCode = 404;
  return {'message': 'not found'};
}

But what about Mongo or Postgres or ?

The other two systems that inspired this project to be kicked off - Aqueduct and Angel - both had some sort of database integration built in.

You do not need this.

Access the dart drivers for the database system you want directly, they all use them behind the scenes:

You will be fine. I have used them this way and they work.

I have rolled my own classes that act as a sort of ORM, especially around Mongo. Its suprisingly effective and doesn't rely on much code.

What I want to do isn't listed

While there are bunch of helpers built in - you have direct access to the low level apis available from the dart:io package. All helpers are just extension methods to:

So you can compose and write any content you can imagine there. If there is something you want to do that isn't expressly listed by the library, you will be able to do it with a minimum of research into underlying libraries. A core part of the architecture is to not build you into a wall.

Websockets

Alfred supports websockets too!

There is a quick chat client in the examples

import 'dart:async';
import 'dart:io';

import 'package:alfred/alfred.dart';
import 'package:alfred/src/type_handlers/websocket_type_handler.dart';

Future<void> main() async {
  final app = Alfred();

  // Path to this Dart file
  var dir = File(Platform.script.path).parent.path;

  // Deliver web client for chat
  app.get('/', (req, res) => File('$dir/chat-client.html'));

  // Track connected clients
  var users = <WebSocket>[];

  // WebSocket chat relay implementation
  app.get('/ws', (req, res) {
    return WebSocketSession(
      onOpen: (ws) {
        users.add(ws);
        users
            .where((user) => user != ws)
            .forEach((user) => user.send('A new user joined the chat.'));
      },
      onClose: (ws) {
        users.remove(ws);
        users.forEach((user) => user.send('A user has left.'));
      },
      onMessage: (ws, dynamic data) async {
        users.forEach((user) => user.send(data));
      },
    );
  });

  final server = await app.listen();

  print('Listening on ${server.port}');
}

Logging

For more details on logging click here.

Print routes

Want to quickly print out the registered routes? (recommended when you fire up the server) call Alfred.printRoutes ie:

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/html', (req, res) {});

  app.printRoutes(); //Will print the routes to the console

  await app.listen();
}

Multi threading and isolates

You can use the app in multithreaded mode. When spawning this way, requests are evenly distributed amongst the various isolates. Alfred is not particularly prescriptive about how you manage the isolates just that "it works" when you fire up multiples.

import 'dart:isolate';

import 'package:alfred/alfred.dart';

Future<void> main() async {
  // Fire up 5 isolates
  for (var i = 0; i < 5; i++) {
    unawaited(Isolate.spawn(startInstance, ''));
  }
  // Start listening on this isolate also
  startInstance(null);
}

/// The start function needs to be top level or static. You probably want to
/// run your entire app in an isolate so you don't run into trouble sharing DB
/// connections etc. However you can engineer this however you like.
///
void startInstance(dynamic message) async {
  final app = Alfred();

  app.all('/example', (req, res) => 'Hello world');

  await app.listen();
}

/// Simple function to prevent linting errors, can be ignored
void unawaited(Future future) {}

Deployment

There are many ways to skin this cat, you can upload the source code to a VPS yourself, build a binary locally and upload it to a server somewhere, but a fairly elegant way to accomplish a production level deployment is to containerize an AOT build of the server and run it on a PAAS.

Lucky there is a tutorial for that! https://ryan-knell.medium.com/build-and-deploy-a-dart-server-using-alfred-docker-and-google-cloud-run-from-start-to-finish-d5066e3ab3c6

Contributions

PRs are welcome and encouraged! This is a community project and as long as the PR keeps within the key principles listed it will probably be accepted. If you have an improvement you would like to to add but are not sure just reach out in the issues section.

Before you submit your code, you can run the ci_checks.sh shell script that will do many of the tests the CI suite will perform.

Comments
  • Please look at pkg:shelf!

    Please look at pkg:shelf!

    https://pub.dev/packages/shelf

    We really encourage folks to build on top of shelf. It's designed to be more easily composed.

    Let me know if you want to chat!

    opened by kevmoo 17
  • Flutter Web HTTP Error: XMLHttpRequest error

    Flutter Web HTTP Error: XMLHttpRequest error

    I have been using Alfred to serve up files to a flutter web app without issue on windows (both flutter web and Alfred server). But, if I try to add HTTP calls from the flutter web app the Alfred server gets the call, but in Flutter Web it errors our Error: XMLHttpRequest.

    Is this because of a CORS issue? Is there a setting I need, a CORS middleware setting?

    opened by jrheisler 12
  • When Multi threading and isolates can be helpful?

    When Multi threading and isolates can be helpful?

    Hi! Could you explain me the situation when multi threading and isolates can be helpful?

    My app have two handlers: /count /sql-upsert /statistic

    I have 4 instance of ParserApp. Every instance after run:

    1. Send count request (DB is very big so it may take up to 10 seconds for every request)
    2. Start processing files and 2.1 sending to sql-upsert result of parsing as SQL plain text statement, 2.2 update status of processed files

    /statistic handler is for big and complex request that run can take up to 30 minutes.

    So could the single isolate (that used by default) be bottleneck in my case? And could isolates be helpful?

    It just seems to me that all IO falls on the database or am I wrong?

    upd: Could be PostgreSQL connection object is bottleneck? If I am using it from handler above? I am using this driver: https://pub.dev/packages/postgres

    opened by bubnenkoff 10
  • How to serve Html, CSS and JS file in a Folder.

    How to serve Html, CSS and JS file in a Folder.

    I have a folder called web which contains HTML, CSS, and Javascript files.

    I need to serve the HTML file with CSS and Javascript as well how can I do that in Alfred.

    something like res.render('index.html') in Node.Js

    The Javascript File and the CSS file or not shown while the HTML is loaded.

    opened by pythonhubdev 10
  • Improve middleware example

    Improve middleware example

    In the README there is the following example:

    import 'dart:async';
    import 'dart:io';
    
    import 'package:alfred/alfred.dart';
    
    FutureOr exampleMiddlware(HttpRequest req, HttpResponse res) {
      // Do work
    }
    
    void main() async {
      final app = Alfred();
      app.all("/example/:id/:name", (req, res) {
        req.params["id"] != null; //true
        req.params["name"] != null; //true;
      }, middleware: [exampleMiddlware]);
    
      await app.listen(); //Listening on port 3000
    }
    
    • From reading this example the order of execution is not clear.
    • The parameter extraction is irrelevant to the example.
    • Maybe adding comments hinting what's executed first
    • The parameter name middleware doesn't fit here, because it does not give a cue on execution order. Maybe introduce before: [exampleMiddleware] or after: [exampleMiddleware] for pre/postprocessing?
    documentation discussion 
    opened by felixblaschke 10
  • Unhandled exception: Bad state: Header already sent

    Unhandled exception: Bad state: Header already sent

    Hello,

    sending any response status code in internal error handler caused an error, when request body is not a valid JSON

    Unhandled exception:
    Bad state: Header already sent
    #0      _HttpResponse.statusCode= (dart:_http/http_impl.dart:1190:35)
    #1      errorHandler (test.dart:18:7)
    #2      Alfred._incomingRequest (package:alfred/src/alfred.dart:419:35)
    <asynchronous suspension>
    #3      _QueuedFuture.execute (package:queue/src/dart_queue_base.dart:26:16)
    <asynchronous suspension>
    

    Code to reproduce

    import 'dart:async';
    import 'package:alfred/alfred.dart';
    
    void main() async {
      final app = Alfred(onInternalError: errorHandler);
    
      app.get('/example', (req, res) {
        final body = req.bodyAsJsonMap;
        res.statusCode = 200;
        return body;
      });
    
      await app.listen();
    }
    
    FutureOr errorHandler(HttpRequest req, HttpResponse res) {
      res.statusCode = 500;
      return {'message': 'error not handled'};
    }
    

    CURL

    curl --request GET \
      --url http://localhost:3000/example \
      --header 'Content-Type: application/json' \
      --data '{
    	"test": true,
    }'
    

    If I get it right, the problem is that body parser already send 400 HTTP code https://github.com/rknell/alfred/blob/master/lib/src/body_parser/http_body.dart#L123 Is it intended behaviour?

    opened by afominov 7
  • Server Directory Traversal - Critical Vulnerabilities

    Server Directory Traversal - Critical Vulnerabilities

    Hello,

    On the backend side, I use a code to access my photos.

    app.get("/assets/files/*", (req, res) => Directory('files/'));

    If I write another link instead of the photo link, I can access whatever file I want to access, which is pretty bad.

    When I go to 127.0.0.1:3000/assets/files/notes.txt we should expect to read notes.txt, it works correctly.

    But if i go to 127.0.0.1:3000/assets/files/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/..%2f/etc/passwd

    I can access the passwd file on my computer and I have not found a way to fix it.

    I think this only happens with ..%2f. if I replace %2f with / I cannot access the passwd file.

    I think there is a vulnerability with %2f.

    opened by seceba 7
  • RouteMatcher & current RegExp

    RouteMatcher & current RegExp

    Route matching is based on regular expressions that are currently built by the RouteMatcher class each time Alfred receives a request. The current implementation also eagerly evaluates all routes even if the first route will do.

    I proposed a change to build the RegExp when the route is created and avoid creating the same RegExp over and over again. I also turned the match() method into a generator to avoid systematic evaluation of all routes.

    I also considered changing the way routes ending with a * segment works but this would be a breaking change. Currently, if the last segment is a *, the RegExp is terminated with:

            /// Generously match path if last segment is wildcard (*)
            /// Example: 'some/path/*' => should match 'some/path'
            matcher += '/?.*';
    

    How would you consider the following change? The idea is to match some/path, some/path/, some/path/whatever, but not some/pathbreaking.

            /// Generously match path if last segment is wildcard (*)
            /// Example: 'some/path/*' => should match 'some/path' but not 'some/pathology'
            matcher += '(?:/.*|)';
    

    I also considered using named groups in the RegExp and capture parameters at match time to avoid additional parsing done in getParams(), but I'm not sure how it would play with NotMatchingRouteException. This would certainly be a breaking change too, although I believe the current implementation is broken because it expects the input Uri to contain as many segment as the route does. This will not work with wildcards eg.

    • route /some/*/thing/:id should match Uri /some/path/to/thing/abc but getParams() will throw (4 segments vs. 5)
    • route /some/thing/:id/* should match Uri /some/thing/abc but getParams() will throw (4 segments vs. 3)

    Leveraging named groups would correctly capture parameters for such routes and getParams() could be removed altogether. If the route matches, there should be no reason why getParams() throws.

    opened by d-markey 7
  • Authentication support

    Authentication support

    Hey, I figured I'd open a tracking ticket, since I like the way this project is progressing. Most server-side frameworks have ways (built-in or third party) to authenticate users; do you think that's something you'd be interested in building out down the line?

    Obviously, there's a cost in terms of complexity and ease of maintenance; on the other hand, authentication is probably the most universal of the currently unmet needs in terms of the Dart server ecosystem.

    enhancement help wanted discussion 
    opened by AKushWarrior 7
  • Replace http_server dependency (Bodyparser)

    Replace http_server dependency (Bodyparser)

    Kevin Moo reported the discontinuity of the http_server package. https://www.reddit.com/r/dartlang/comments/mkafju/palace_server_side_dart_framework/gtmmbp4?utm_source=share&utm_medium=web2x&context=3

    https://pub.dev/packages/http_server

    opened by felixblaschke 7
  • onNotFound handler is not functional

    onNotFound handler is not functional

    While reviewing the routing algorithms I noticed a flaw which makes onNotFound handler not working as intended.

    Alfred looks for any matching route. If he doesn't find any, it's a "not found" for him. This assumption is wrong because any middleware acts like an effective route. This results in using any middleware makes the onNotFound handler not functional.

    Here is an example:

    import 'package:alfred/alfred.dart';
    
    Future<void> main() async {
      final app = Alfred(
        onNotFound: (req, res) => '404'
      );
    
      app.all('*', (req, res) { // mandatory middleware
        res.headers.set('X-Powered-By', 'Alfred');
      });
    
      app.get('/valid', (req, res) => 'Valid route');
    
      await app.listen();
    
      // GET 'http://localhost:3000/notvalid' => no 404
    }
    

    I tried to figure out the perfect moment for saying "not found". I realized the location where Alfred checks non-done request and prints out a warning is perfect for being "not found":

            /// If you got here and isDone is still false, you forgot to close
            /// the response, or your didn't return anything. Either way its an error,
            /// but instead of letting the whole server hang as most frameworks do,
            /// lets at least close the connection out
            ///
            if (!isDone) {
              if (request.response.contentLength == -1) {
                print(
                    "Warning: Returning a response with no content. ${effectiveRoutes.map((e) => e.route).join(", ")}");
              }
              await request.response.close();
            }
    
    discussion 
    opened by felixblaschke 7
  • Router callback handlers are not async-safe

    Router callback handlers are not async-safe

    Let met try to explain what I mean by "async-safe".

    My expectation for serving a page is that the response is

    1. returned/written only when all async computation that has finished for a given request finished running
    2. can capture any all all async computation exceptions before a response would be returned to the browser

    Let me demonstrate it:

    import 'dart:async';
    import 'dart:io';
    import 'dart:math';
    
    import 'package:alfred/alfred.dart';
    
    final rnd = Random.secure();
    
    void main() async {
      final app = Alfred(
        onInternalError: (req, resp) {
          stderr.writeln('Internal error occured for ${req.store.get('rid')}');
        },
        logLevel: LogType.debug,
      );
      app.all('*', (req, res) {
        final id = (rnd.nextInt(8999999) + 1000000).toRadixString(36);
        print('Request ID: $id');
        req.store.set('rid', id);
      });
    
      app.get('/err', (req, res) async {
        Future.delayed(Duration(seconds: 5), () async {
          throw 'ASYNC ERR';
        });
        return 'Returned before all request related computation finished: ${req.store.get('rid')}';
      });
    
      app.printRoutes();
      await app.listen();
    
      print('Server is listening on http://localhost:3000');
    }
    

    In this very specific instance, I'd expect the following to happen:

    1. onInternalError catches ASYNC ERR - it does not
    2. before /err's return value would be written (sent back to browser), delayed future's result is awaited
    3. server does not crash - it does

    While this can be worked around and lint rules set up (unawaited_futures eg.), it is very likely that eventually some random future will crash the server which will be very hard to track down without the capability of catching it.

    A solution I saw being used is that all code that works on requests or responses are ran within it's own error zone (runZonedGuarded). With a custom Zone implementaiton, you could even give super useful warning messages that request returned before all futures would have completed (similar to how test package tracks them).

    This would give me a sense of security.

    opened by daniel-v 1
  • Enchant: Class-Based Middlewares and Endpoint callback

    Enchant: Class-Based Middlewares and Endpoint callback

    Thanks for this amazing idea! Inspired by this Issue : https://github.com/rknell/alfred/issues/88

    With this Mixin we can create class-based middlewares

    class _ExampleMiddleware with CallableRequestMixin{
      @override
      FutureOr call(HttpRequest req, HttpResponse res) {
        if (req.headers.value('Authorization') != 'apikey') {
          throw AlfredException(401, {'message': 'authentication failed'});
        }
      }
    }
    
    void main() async {
      final app = Alfred();
      app.all('/example/:id/:name', (req, res) {}, middleware: [_ExampleMiddleware()]);
    
      await app.listen(); //Listening on port 3000
    }
    

    and End-Point callback too !

    class _RequestController with CallableRequestMixin {
      @override
      FutureOr call(HttpRequest req, HttpResponse res) => "I'm a Class :)";
    }
    
    void main() async {
      final app = Alfred();
    
      app.get('/text', _RequestController());
    
      await app.listen(6565); //Listening on port 6565
    }
    
    opened by NathanDraco22 2
  • If the same key name in formdata

    If the same key name in formdata

    Hello,

    I am trying to send multiple keys/value with the same key name using the post method using form-data.

    However, I can only get the last one of the values with the same key name.

    My goal is to upload images to the server using the same key like file[] name but I can only get the last one.

    How can I handle form-data keys using the same key name?

    Form-Data looks like this.

    name = "John" Age = 24 file[] = Image1.jpg file[] = Image2.jpg file[] = Image3.jpg file[] = Image4.jpg

    Whenever I check the incoming data, only image4 is read. What I want to do is to loop and process how many file[] there are.

    When sending the Form-Data, I can make the key names as file[1] file[2], but I wonder why I can't do this by sending the same name, is this feature not supported or did I fail.

    PS: Thank you very much for the beautiful package, it is a great job.

    opened by seceba 2
  • class-based Middlewares

    class-based Middlewares

    This is not an issue with the package, but an example of a way you could implement Middlewares very cleanly:

    The idea, I got from https://github.com/vandadnp/flutter-tips-and-tricks#callable-classes-in-dart.

    // login_middleware.dart
    import 'package:alfred/alfred.dart';
    
    class LoginMiddleware {
      const LoginMiddleware();
    
      Future<dynamic> call(HttpRequest req, HttpResponse res) async {
        final body = await req.bodyAsJsonMap;
        final email = body['email'] as String?;
        final password = body['password'] as String?;
    
        // you can add more requirements if you like
        if (email == null || password == null) {
          res.statusCode = 400;
          return {'error': 'email and password are required'};
        }
    
        // save the variables in store to grab them later inside the callback function
        req.store.set('email', email);
        req.store.set('password', password);
    
        return null;
      }
    }
    

    and here's the implementation of this:

    import 'package:alfred/alfred.dart';
    import './auth_validator.dart';
    
    final app = Alfred().post(
      '/login',
      (req, res) {
        // your callback function
      },
      middleware: [
        const LoginValidator(),
      ],
    );
    

    I hope this is useful to someone :))

    documentation 
    opened by tomassasovsky 2
  • how I upload big file?

    how I upload big file?

    Hello. First of all, thank you for this useful package.

    I want to get large files, for example 2 GB. Is the file received in memory? Is it possible for me to give the file path to be saved in pieces?

    opened by ali-1989 1
Owner
Ryan Knell
Ryan Knell
A Deep Learning Based Attendance System is a mobile application that aims to make it easier for lecturers to check the attendance status of students which are attending the course.

Attendance System / Flutter App A Deep Learning Based Attendance System is a mobile application that aims to make it easier for lecturers to check the

Merthan Kavak 11 Oct 24, 2022
This app is designed to make the preparation for see easier.

science A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

Nishant Pokhrel 2 Jun 20, 2022
Calculate your pizza recipe in a few seconds! 🍕

Calcolapizza Calcolapizza lets you calculate the exact recipe for your pizza doughs in a few seconds. Set how many dough balls you want to make, their

Domenico Majorana 44 Dec 26, 2022
To cure symptoms of various disease using medicines at home this app will act as guideline. Few animations are used and Firebase is used as database.

Medkit It's a Pharmacy application that help you in curing basic symptoms and diseases with medicines available in your home. How to Run Code Clone or

Muhammad Hamza 109 Dec 22, 2022
I was challenged to create an app with only the Nubank life insurance part and fix some UI issues.

Seguros I was challenged to create an App with only the Nubank life insurance part and fix some UI issues. Flutter This is a Flutter App, designed bas

Caio Moura 14 Dec 14, 2022
This is a todo app for managing your tasks and life. Built with Flutter

todo_app A new Flutter project. Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

null 0 Oct 30, 2021
A task manager app made with flutter where you can mark your tasks for specific days and keep your life organized!

task_manager_app A task manager app made with flutter where you can mark your tasks for specific days and keep your life organized! Getting Started Th

null 2 Sep 1, 2021
server side dart micro-framework to handle incoming http requests

Queen Palace ???? Introduction server side dart micro-framework to handle incoming http requests

Ahmed Masoud 32 Aug 30, 2022
Rokeet UI - A Server Driven UI Framework

Rokeet UI A Server-Driven UI framework. Status Lib Build Coverage Core Configure Mockoon server Install Mockoon: Mac OSX brew install --cask mockoon

osodroid 6 Apr 7, 2022
A Flutter server rendering framework

Shark Flutter ?? (Under Construction) A Flutter server rendering framework After i finish the project structure, I would draw a project diagram and de

Vau 62 Dec 29, 2022
An expressive, functional, and full-featured server-side framework for Dart.

A framework and collection of packages for writing http servers, built on top of the shelf package. This framework is intended to reduce the technical

Marathon 45 Jun 25, 2022
Modular server framework with ConnectMe (WebSockets + PackMe) and MongoDb support.

What is ServeMe ServeMe is a simple and powerful modular server framework. It allows to easily create backend services for both mobile and web applica

Source Caster 20 Dec 3, 2022
Dart HTTP server framework for building REST APIs. Includes PostgreSQL ORM and OAuth2 provider.

Aqueduct is a modern Dart HTTP server framework. The framework is composed of libraries for handling and routing HTTP requests, object-relational mapp

Stable Kernel 2.4k Jan 5, 2023
This app is a minimal TodoList app that functions like a whiteboard. You can add new tasks, keep track of your tasks to make your day more productive, and then delete it after it is done.

My First Mobile App _ Minimal TodoList Flutter A new Flutter project. Getting Started This project is a starting point for a Flutter application. A fe

null 0 Nov 23, 2021
Routinger is a task scheduler app that is made to make you a better person at no extra cost. The code is open-source. Dart language and Flutter framework are used extensively.

Routinger This is a simple app that will allow you to schedule your tasks, create a simple to-do, and also make recurring tasks. The app ends you noti

Routinger 16 Dec 17, 2022
FileManager is a wonderful widget that allows you to manage files and folders, pick files and folders, and do a lot more. Designed to feel like part of the Flutter framework.

File Manager FileManager is a wonderful widget that allows you to manage files and folders, pick files and folders, and do a lot more. Designed to fee

Devs On Flutter 52 Dec 30, 2022
Actor model implementation in Dart. This package makes it easier to work with isolates, create clusters of isolates.

Actor model implementation in Dart Languages: Introduction About Theater Installing What is Actor Notes about the actors Actor system Actor tree Using

Gleb Batykov 39 Nov 14, 2022
Flutter Package for Easier Creation of Home Screen Widgets

Home Widget HomeWidget is a Plugin to make it easier to create HomeScreen Widgets on Android and iOS. HomeWidget does not allow writing Widgets with F

Anton Borries 405 Dec 31, 2022
Nakama is an open-source server designed to power modern games and apps

Nakama is an open-source server designed to power modern games and apps. Features include user accounts, chat, social, matchmaker, realtime multiplayer, and much more.

Allan Nava 85 Dec 30, 2022