Skip to content

A lightweight Sinatra/Express-style Java web framework -- zero-dependency embedded HTTP server or Jakarta Servlet deployment

License

Notifications You must be signed in to change notification settings

ghosthack/turismo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

102 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

turismo

A lightweight Sinatra/Express-style Java web framework.

CI Javadocs Maven Central

Maven

<dependency>
    <groupId>io.github.ghosthack</groupId>
    <artifactId>turismo</artifactId>
    <version>3.1.0</version>
</dependency>

Gradle:

implementation 'io.github.ghosthack:turismo:3.1.0'

Requires Java 17+.

Note: Versions 1.x were published under com.ghosthack:turismo. The groupId changed to io.github.ghosthack starting with 2.0.0.

Quick start

Zero dependencies -- uses the JDK's built-in HTTP server:

import static io.github.ghosthack.turismo.Turismo.*;

public class App {
    public static void main(String[] args) {
        get("/hello", "Hello World!");
        get("/users/:id", () -> print("User ", param("id")));
        post("/users", () -> json(Map.of("created", true)));
        start(8080);
    }
}

Routing

Exact paths

get("/hello", "Hello!");
post("/submit", () -> print("Created")); // defaults to 201

Named parameters

get("/users/:id", () -> {
    String id = param("id");
    print("User " + id);
});

get("/users/:userId/posts/:postId", () -> {
    print(param("userId") + "/" + param("postId"));
});

Wildcards

get("/files/*/download", () -> print("Downloading"));

Query parameters

get("/search", () -> {
    String q = param("q"); // from ?q=turismo
    print("Search: " + q);
});

HTTP methods

All standard methods: get, post, put, delete, patch, head, options.

POST routes default to status 201 (Created):

post("/data", () -> print("created"));        // 201
delete("/users/:id", () -> print("Deleted ", param("id")));

Response helpers

// Set status code
status(201);

// Set headers
header("X-Custom", "value");
type("application/json");

// Write body
print("Hello World");
print("Hello ", name, "!");  // varargs — avoids concatenation

// JSON response (built-in serializer, no dependencies)
json(Map.of("ok", true, "count", 42));
json(List.of("a", "b", "c"));
String s = toJson(Map.of("key", "value")); // serialize without writing

// Redirects
redirect("/new-location");       // 302
movedPermanently("/new-url");    // 301
redirect(307, "/temporary");     // custom code

// 404
notFound();

Custom not-found handler

notFound(() -> {
    status(404);
    type("application/json");
    print("{\"error\":\"not found\"}");
});

Request access

get("/echo", () -> {
    String method = method();            // HTTP method
    String path = path();                // request path
    String auth = header("Authorization"); // request header
    InputStream body = body();           // request body
});

Servlet deployment

turismo also supports deployment in any Jakarta EE 10 servlet container (Tomcat 10.1+, Jetty 12+, etc.) via the Servlet class and RoutesMap/RoutesList API.

RoutesMap — exact match (O(1) lookup)

import io.github.ghosthack.turismo.action.Action;
import io.github.ghosthack.turismo.routes.RoutesMap;

public class AppRoutes extends RoutesMap {
    @Override
    protected void map() {
        get("/hello", new Action() {
            @Override
            public void run() {
                print("Hello!");
            }
        });
    }
}

RoutesList — wildcards and named parameters

import io.github.ghosthack.turismo.routes.RoutesList;

public class AppRoutes extends RoutesList {
    @Override
    protected void map() {
        get("/users/:id", new Action() {
            @Override
            public void run() {
                print("User " + params("id"));
            }
        });

        // Route aliases
        get("/u/:id", "/users/:id");
    }
}

Embedded Jetty

import io.github.ghosthack.turismo.servlet.Servlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Server;

public class Main {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);
        ServletContextHandler ctx = new ServletContextHandler();
        ctx.setContextPath("/");
        ServletHolder holder = new ServletHolder(new Servlet());
        holder.setInitParameter("routes", "com.example.AppRoutes");
        ctx.addServlet(holder, "/*");
        server.setHandler(ctx);
        server.start();
        server.join();
    }
}

web.xml

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.0">

  <servlet>
    <servlet-name>app</servlet-name>
    <servlet-class>io.github.ghosthack.turismo.servlet.Servlet</servlet-class>
    <init-param>
      <param-name>routes</param-name>
      <param-value>com.example.AppRoutes</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>app</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

JSP rendering

get("/render", new Action() {
    @Override
    public void run() {
        req().setAttribute("message", "Hello World!");
        jsp("/WEB-INF/views/render.jsp");
    }
});

Multipart file uploads

import io.github.ghosthack.turismo.multipart.MultipartRequest;

post("/upload", new Action() {
    @Override
    public void run() {
        try {
            MultipartRequest multipart = MultipartRequest.wrapAndParse(req());
            String[] meta = multipart.getParameterValues("image");
            String contentType = meta[0];
            String fileName = meta[1];
            byte[] bytes = (byte[]) multipart.getAttribute("image");
            print("Uploaded " + fileName + " (" + bytes.length + " bytes)");
        } catch (Exception e) {
            throw new ActionException(e);
        }
    }
});

Releasing

  1. Set the release version in pom.xml (remove -SNAPSHOT)
  2. Commit, push, and merge via PR
  3. CI detects the version change, creates a GitHub release, and deploys to Maven Central
  4. Publish the deployment at https://central.sonatype.com/publishing/deployments

License

Apache License 2.0

About

A lightweight Sinatra/Express-style Java web framework -- zero-dependency embedded HTTP server or Jakarta Servlet deployment

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 5