CORS for a frontend
A frontend often runs on one address, for example http://localhost:5173 (the Vite dev server), and the API on another, http://localhost:8080. By default a browser does not let JavaScript from a page of one origin (scheme, domain and port) read responses from another. The mechanism that lets a server allow such requests is called CORS (Cross-Origin Resource Sharing). Without it, fetch("http://localhost:8080/posts") on a frontend page fails with an error in the browser console.
How CORS works
- The browser adds an
Originheader with the page address to the request. - For "non-simple" requests, such as
POSTwith JSON or with anAuthorizationheader, the browser first sends a preflight request with theOPTIONSmethod. - The server answers with an
Access-Control-Allow-Originheader. If the origin is not there, the browser does not give the response to the page.
curl and other programs do not check CORS rules — it is a protection of the browser.
The CORS plugin
Javalin has a bundled plugin. Enable it in the configuration:
// src/main/java/com/example/blog/Main.java
config.bundledPlugins.enableCors(cors -> {
cors.addRule(rule -> rule.allowHost("http://localhost:5173"));
});addRule adds a rule, allowHost lists the allowed origins. The plugin answers OPTIONS requests by itself and adds the needed headers.
Checking it
Let us pretend to be a browser and send an Origin header.
curl -i -H "Origin: http://localhost:5173" http://localhost:8080/postsHTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
[{"id":1,"title":"Hello, H2","body":"Posts now live in a database.","author":"Oleg"}]
Now the preflight request that the browser sends before a POST with a password:
curl -i -X OPTIONS http://localhost:8080/posts \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type, authorization"HTTP/1.1 200 OK
Content-Type: text/plain
Access-Control-Allow-Headers: content-type, authorization
Access-Control-Allow-Methods: POST
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin
The server allowed the method and the headers, and the browser will send the real request.
Other rule settings
| Setting | What it does |
|---|---|
rule.allowHost("https://blog.example.com") | Allow specific origins |
rule.allowHost("*.example.com") | Allow all subdomains |
rule.anyHost() | Allow everyone (the * header) |
rule.allowCredentials = true | Allow cookies and the Authorization header in browser requests |
rule.exposeHeader("X-Total") | Make a response header visible to scripts |
rule.path = "posts*" | Apply the rule only to some addresses |
Common mistake
If the origin is not in the list, the plugin answers with code 400, although the response body is there:
curl -i -H "Origin: http://evil.example" http://localhost:8080/postsHTTP/1.1 400 Bad Request
Content-Type: application/json
[{"id":1,"title":"Hello, H2","body":"Posts now live in a database.","author":"Oleg"}]
There is no Access-Control-Allow-Origin header, so the browser does not give the response to the page. When you see a CORS error in the browser console, first compare the page address with allowHost down to the port and scheme: http://localhost:5173 and http://127.0.0.1:5173 are different origins.