API Documentation

BeerConfig Class

The BeerConfig class represents the configuration for the Beer server, including IP, port, Jetty thread pool, SSL, and logger settings. All setters return BeerConfig for method chaining.

Full beer config example

new BeerConfig(  )
    .setIp( "0.0.0.0" )
    .setPort( 8080 )
    .setLoggerName( "beer" )
    .setJettyMinThreads( 4 )
    .setJettyMaxThreads( 32 )
    .setJettyIdleTimeout( 60000 );
    .setKeystorePath( "path/to/keystore" ); // For HTTPS
    .setKeystorePass( "yourPassword" );
                    

Beer Class

init( BeerConfig config )

Initializes the Beer server with the given configuration.

  • config: BeerConfig object containing server settings like IP, port, thread pool, and SSL configuration.
Beer beer = new Beer(  );
BeerConfig config = new BeerConfig(  );
beer.init( config );

start( )

Starts the server and binds all configured routes and filters.

beer.start(  );

getServerInstance( )

Returns the underlying Jetty Server instance for advanced manipulation.

Server server = beer.getServerInstance(  );

REST Methods

Define HTTP routes for GET, POST, PUT, DELETE requests.

  • get( String path, RequestHandler handler ): Register a GET endpoint.
  • post( String path, RequestHandler handler ): Register a POST endpoint.
  • put( String path, RequestHandler handler ): Register a PUT endpoint.
  • delete( String path, RequestHandler handler ): Register a DELETE endpoint.
beer.get( "/api/users", ( req, res ) -> { ... } );
beer.post( "/api/users", ( req, res ) -> { ... } );

Filters

Define HTTP routes for preprocess requests.

filter( String path, FilterHandler handler )

Registers a filter ( middleware ) for a specific path.

beer.filter( "/api/*", ( req, res ) -> {
    System.out.println( req.getRequestURI(  ) );
} );

Beer predefined available filters

  • exceptionFilter( )

    Adds global exception handling, returning proper HTTP error codes and messages. Beer auto generates an error tag and a basic error message for corresponding error code.

    In case of custom error responses do not use this filter and implement your own exception filter at "/*" path using filter( ) method.

  • corsAllFilter( )

    Enables CORS for all routes, allowing cross-origin requests.

  • loggingFilter( )

    Logs all HTTP requests to the configured logger.

Web Sockets

Define WebSockets with ease. Beer has an internal map that stores connected clients for you.

socket( String path, Consumer<Object> onMessage )

Registers a WebSocket endpoint with a callback for incoming messages.

  • onMessage: Lambda to handle messages from clients.
beer.socket( "/ws/chat", msg -> System.out.println( msg ) );

broadcast( String path, Object data )

Sends a message to all connected WebSocket sessions on a specific path.

beer.broadcast( "/ws/chat", Map.of( "message", "Hello everyone!" ) );

Static Files

Define static file path. Must be a folder inside standalone fat jar.

staticFiles( String path, String filePathInClasspath )

Serves static files from the classpath at the specified path.

beer.staticFiles( "/docs", "/static" );

BeerUtils Class

parseReqBody( HttpServletRequest req )

Reads and returns the raw body of an HTTP request as a String.

  • req: The HTTP request.
String body = BeerUtils.parseReqBody( req );

parseBody( HttpServletRequest req, Class<?> clazz )

Parses the request body JSON into an object of the specified class.

  • clazz: Class to parse the body into.
MyObject obj = BeerUtils.parseBody( req, MyObject.class );

parseQueryParams( HttpServletRequest req, Class<?> clazz )

Parses query parameters into an object of the specified class.

MyParams params = BeerUtils.parseQueryParams( req, MyParams.class );

getPathParam( HttpServletRequest req, String paramName )

Retrieves a path parameter value from the request.

String userId = BeerUtils.getPathParam( req, ":userid" );

parseBasicAuthCredentials( HttpServletRequest req )

Parses Authorization header and returns username/password credentials.

Credentials creds = BeerUtils.parseBasicAuthCredentials( req );

setReqUser( HttpServletRequest req, Object user )

Stores a user object in the request attributes for later retrieval.

BeerUtils.setReqUser( req, user );

getReqUser( HttpServletRequest req )

Retrieves a user object previously stored in the request.

User user = BeerUtils.getReqUser( req );

getPathSegment( HttpServletRequest req, int index )

Returns a specific segment of the request URI.

String segment = BeerUtils.getPathSegment( req, 2 );

redirect( HttpServletResponse res, String url )

Sends an HTTP redirect to the client.

BeerUtils.redirect( res, "/home" );

json( Object object )

Converts an object, collection, map, or string into a JSON-formatted string.

String jsonString = BeerUtils.json( myObject );

BeerFileUtils Class

uploadFile( HttpServletRequest req, HttpServletResponse res, String partName, String uploadLocation )

Uploads a file from a multipart request to the specified location using default configuration.

  • partName: Name of the file part in the request.
  • uploadLocation: Destination folder path for uploaded files.
SimpleMessage msg = BeerFileUtils.uploadFile( req, res, "file", "/uploads" );

uploadFile( HttpServletRequest req, HttpServletResponse res, String partName, String uploadLocation, BeerFileConfig config )

Uploads a file with a custom configuration for maximum file size, request size, and file threshold.

BeerFileConfig config = new BeerFileConfig(  ).setMaxFileSize( 5000000 );
SimpleMessage msg = BeerFileUtils.uploadFile( req, res, "file", "/uploads", config );

downloadFile( HttpServletRequest req, HttpServletResponse res, String uploadLocation, String path, Boolean isInline )

Downloads a file from the server, either inline or as an attachment.

  • uploadLocation: Folder where the file is stored.
  • path: File name or path relative to upload location.
  • isInline: true to display in browser, false to force download.
BeerFileUtils.downloadFile( req, res, "/uploads", "file.png", true );

deleteFile( HttpServletRequest req, HttpServletResponse res, String uploadLocation, String path )

Deletes a file from the server.

SimpleMessage msg = BeerFileUtils.deleteFile( req, res, "/uploads", "file.png" );

listFiles( String uploadLocation )

Lists all files in a directory, sorted by last modification date.

List<String> files = BeerFileUtils.listFiles( "/uploads" );

BeerAuthUtils Class

generateRandomSecretKey( )

Generates a random secret key using SHA-256 hash of a UUID, suitable for JWT signing.

String secretKey = BeerAuthUtils.generateRandomSecretKey(  );

generateJwt( String secretKey, Integer expirationMinutes, String userId )

Generates a signed JWT token with a subject and expiration time.

  • secretKey: Secret key used to sign the token.
  • expirationMinutes: Token expiration time in minutes.
  • userId: Subject of the token ( usually the user ID ).
String jwtToken = BeerAuthUtils.generateJwt( secretKey, 60, "user123" );

verifyJwt( String secretKey, String jwtToken )

Verifies a JWT token and returns its claims. Throws UnauthorizedException if invalid or expired.

  • secretKey: Secret key used to verify the token.
  • jwtToken: JWT token string to verify.
JWTClaimsSet claims = BeerAuthUtils.verifyJwt( secretKey, jwtToken );

Real World Example

A fully functional production ready real world application made with beer in just one file and a few lines of code. For database operations library sqlemur has been used.

package gr.kgdev.emsrally;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

import org.apache.commons.codec.digest.DigestUtils;

import gr.kgdev.beer.core.Beer;
import gr.kgdev.beer.core.BeerConfig;
import gr.kgdev.beer.model.SimpleMessage;
import gr.kgdev.beer.model.exceptions.BadRequestException;
import gr.kgdev.beer.model.exceptions.ForbiddenException;
import gr.kgdev.beer.model.exceptions.UnauthorizedException;
import gr.kgdev.beer.utils.BeerAuthUtils;
import gr.kgdev.beer.utils.BeerUtils;
import gr.kgdev.beerutils.PropertiesLoader;
import gr.kgdev.emsrally.model.EmsRallyUser;
import gr.kgdev.emsrally.model.EmsRallyUserRole;
import gr.kgdev.emsrally.model.Management;
import gr.kgdev.emsrally.model.SaveScoresBody;
import gr.kgdev.emsrally.model.Scenario;
import gr.kgdev.emsrally.model.SaveScoresBody.Score;
import gr.kgdev.sqlemur.core.SqlConnector;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
                        

public class EmsRallyBeerApp {

    private static final String ROLES_QUERY = 
    """
    select r.name 
    from user_roles r, user_has_role uhr, users u 
    where 1=1 
    and uhr.user_id = u.id 
    and r.id = uhr.role_id 
    and u.id = ?
    """;
    private Beer beer = new Beer(  );
    private SqlConnector sqlConnector;
    private String secretKey;

    public static void main( String[] args ) throws Exception {
        new EmsRallyBeerApp(  ).start(  );
    }
    
    private void start(  ) throws Exception {
        String ip = PropertiesLoader.getProperty( "server.ip", String.class );
        Integer port = PropertiesLoader.getProperty( "server.port", Integer.class );
        String dbUrl = PropertiesLoader.getProperty( "db.url", String.class );
        String dbUsername = PropertiesLoader.getProperty( "db.username", String.class );
        String dbPassword = PropertiesLoader.getProperty( "db.password", String.class );
        
        sqlConnector = new SqlConnector( "com.mysql.cj.jdbc.Driver", dbUrl, dbUsername, dbPassword );
        sqlConnector.enableCcMode( true );
        
        secretKey = BeerAuthUtils.generateRandomSecretKey(  );
        
        var config = 
            new BeerConfig(  ).setIp( ip )
                .setPort( port )
                .setLoggerName( "beer" )
                .setJettyMinThreads( 4 )
                .setJettyMaxThreads( 32 )
                .setJettyIdleTimeout( 60000 );
        
        beer.init( config );
        
        // FILTERS
        beer.corsAllFilter(  );
        beer.exceptionFilter(  );
        // authorization filter
        beer.filter( "/api/*", this::authorize );
        beer.filter( "/api/admin/*", this::authorizeAdmin );
        
        // FILES
        beer.staticFiles( "/", "/static" );
        
        // ENDPOINTS
        beer.post( "/login", this::login );
        beer.get( "/api/teams", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams" ) );
        beer.get( "/api/scenarios", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_scenarios" ) );
        beer.get( "/api/scenarios/:id", this::getScenario );
        beer.post( "/api/scores", this::postScores );
        
        // admin routes
        beer.get( "/api/admin/total-scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_total_scores" ) );
        beer.get( "/api/admin/teams-total-scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams_total_scores" ) );
        beer.get( "/api/admin/teams", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_teams" ) );
        beer.get( "/api/admin/scores", ( req, res ) -> sqlConnector.executeQueryToList( "select * from scores" ) );
        beer.get( "/api/admin/managements", ( req, res ) -> sqlConnector.executeQueryToList( "select * from v_managements" ) );
        
        beer.start(  );
    }
    
    private void authorize( HttpServletRequest req, HttpServletResponse res ) throws Exception {
        var authorizationHeader = req.getHeader( "Authorization" );
        if ( authorizationHeader == null ) {
            throw new BadRequestException( "No authorization header" );
        }
        
        var claims = BeerAuthUtils.verifyJwt( secretKey, authorizationHeader.replace( "Bearer ", "" ) );
        
        var user = ( EmsRallyUser )
            sqlConnector.executeQueryToObject( 
                "select * from users where id = ?",
                Arrays.asList( claims.getSubject(  ) ), 
                EmsRallyUser.class
             );
        
        var isUserInactive = user.getStatus(  ) == 0;
        if ( isUserInactive )
            throw new ForbiddenException( "User is inactive" );
        
        List roles = sqlConnector.executeQueryToList( 
                ROLES_QUERY,
                Arrays.asList( user.getId(  ) ), EmsRallyUserRole.class
             );
        
        user.setRoles( roles.stream(  ).map( role -> role.getName(  ) ).toList(  ) );
        
        BeerUtils.setReqUser( req, user );
    }
    
    private void authorizeAdmin( HttpServletRequest req, HttpServletResponse res ) throws Exception {
        var user = ( EmsRallyUser ) BeerUtils.getReqUser( req );
        var isAdmin = user.getRoles(  ).contains( "Admin" );
        
        if ( !isAdmin ) {
            throw new ForbiddenException( "User is not admin" );
        }
    }
    
    private EmsRallyUser login( HttpServletRequest req, HttpServletResponse res ) throws Exception {
        var creds = BeerUtils.parseBasicAuthCredentials( req );

        EmsRallyUser user = sqlConnector.executeQueryToObject( 
                "select * from users where username = ? and password = ?",
                Arrays.asList( 
                    creds.getUsername(  ), 
                    DigestUtils.sha1Hex( creds.getPassword(  ) )
                 ),
                EmsRallyUser.class );

        if ( user == null )
            throw new UnauthorizedException( "User credentials are wrong!" );

        List roles = sqlConnector.executeQueryToList( 
                ROLES_QUERY,
                Arrays.asList( user.getId(  ) ), EmsRallyUserRole.class
             );
        
        user.setRoles( roles.stream(  ).map( role -> role.getName(  ) ).toList(  ) );
        user.setToken( BeerAuthUtils.generateJwt( secretKey, 1440, user.getId(  ).toString(  ) ) );

        return user;
    }
    
    private Scenario getScenario( HttpServletRequest req, HttpServletResponse res ) throws Exception {
        var scenarioId = Integer.parseInt( BeerUtils.getPathParam( req, "id" ) );
        Scenario scenario = sqlConnector.executeQueryToObject( 
                """
                select * from scenarios as s
                where 1=1
                and s.id = ?
                """,
                Arrays.asList( scenarioId ),
                Scenario.class );
        
        List managements = 
                sqlConnector.executeQueryToList( 
                        """
                        select m.*
                        from 
                            managements_v2 as m, 
                            scenario_has_managements as shm
                        where 1=1 
                        and shm.management_id = m.id
                        and shm.scenario_id = ?
                        """, 
                        Arrays.asList( scenarioId ), 
                        Management.class );
        
        scenario.setManagements( managements );
        return scenario;
    }
    
    private SimpleMessage postScores( HttpServletRequest req, HttpServletResponse res ) throws Exception {
        SaveScoresBody body = BeerUtils.parseBody( req, SaveScoresBody.class );
        body.validate(  );
    
        var user = ( EmsRallyUser ) BeerUtils.getReqUser( req );

        // generate a batch id from current timestamp in order
        // to know which scores provided together
        var batchId = DigestUtils.sha256Hex( LocalDateTime.now(  ).toString(  ) + user.getId(  ).toString(  ) );
        
        for ( Score score: body.getScores(  ) ) {
            sqlConnector.executeUpdate( 
                    "insert into scores_v2 ( batch_id, team_id, management_id, score, comment, evaluator_id ) values ( ?, ?, ?, ?, ?, ? )",
                    Arrays.asList( 
                        batchId,
                        body.getTeamId(  ), 
                        score.getManagementId(  ), 
                        score.getScore(  ), 
                        score.getComment(  ), 
                        user.getId(  ) )
                     );
        }
        
        sqlConnector.executeUpdate( 
                "insert into comments ( team_id, scenario_id, evaluator_id, comments ) values ( ?, ?, ?, ? )",
                Arrays.asList( 
                    body.getTeamId(  ), 
                    body.getScenarioId(  ), 
                    user.getId(  ), 
                    body.getComments(  ) 
                 )
         );
        
        return new SimpleMessage( "Scenario has been submitted!" );
    }
    
}