Today, I’ll show you how to detect whether an HTTP request was submitted via form or with to the server. HTML JavaScript The examples in this post are based on and include references to a few global functions ( , , ). It’s not important that you know how they work. Just focus on the concept. I’ll explicitly highlight the important bits. Nuxt.js defineEventHandler getRequestHeaders sendRedirect Here’s a very basic event handler for a Nuxt JS server. An “event handler” in Nuxt.js represents an HTTP endpoint. We can write an event handler to create a API endpoint that generates and returns an object. export default defineEventHandler(async (event) => { // Do some work; Get some data const data = { goodBoy: 'Nugget' }; return data; }); That object will be converted to a JSON string for the HTTP response. The author of this story works at Akamai and thus may have a vested interest in the products or tools mentioned below. What’s The Problem? Again, the syntax and underlying framework is not important. The important thing to know is that requests made with JavaScript can handle JSON responses just fine, but it’s a different story for requests made with HTML forms. When a form is submitted the browser sends the request as a page navigation, causing the next page to render whatever the server responds with. If the response is JSON, it will render that JSON string to the page. Not a great user experience. Now , if we’re building a JSON API, why should we care about HTML forms? And the answer is, even if you prefer using JavaScript to submit data, there are . So it’s a good idea to by using JavaScript to enhance HTML forms in such a way that if JavaScript fails, the HTTP request falls back to a normal form submission. The user experience is not as sexy, but at least it’s not broken. you may ask yourself many scenarios in which JavaScript fails implement progressive enhancement So how can we fix the JSON response issue? The Ideal Option If our request handler could detect whether a request was submitted with an HTML form, we could respond with an HTTP redirect telling the browser to load a specific URL. If the request was submitted with JavaScript, we could go ahead and return JSON. There’s an HTTP header called , which browsers automatically include with every HTTP request (including HTML forms and JavaScript). We can use this header to distinguish between one or the other. Sec-Fetch-Mode The header can have the following “ ” or values: directives cors navigate no-cors same-origin websocket We’re interested in because it indicates an HTTP request was made via HTML page navigation. The value is also send during HTML from submissions and it’s not allowed to be used in requests. navigate navigate fetch With that in mind, we can modify our code to check if the request was sent via HTML form. If so, we can redirect the user. Here’s how it might look: export default defineEventHandler(async (event) => { // Do some work; Get some data const data = { goodBoy: 'Nugget' }; const headers = getRequestHeaders(event); const isHtml = headers['sec-fetch-mode'] === 'navigate'; if (isHtml) { return sendRedirect(event, String(headers.referer), 303); } return data; }); For whatever API you are building, you’ll probably start with some business logic that ultimately generates an object. The important part is what comes next. With the HTTP request , we can check if the header is set to navigate. If so, we know that this was submitted with HTML. headers Sec-Fetch-Mode (IMPORTANT: Nuxt.js converts all headers to lowercase, but it’s technically valid to sent capitalized headers as well. Make sure to account for that in your application.) If the request was submitted with HTML, it doesn’t make sense to return JSON. So instead, we can send a redirect response back to the browser. 303 If you know where to redirect to, you’re all set. Many JSON APIs won’t know. In that case, it makes sense to send the request back to the page it came from using the header. referer The Practical Option (Today) Now, this is great, but there’s one problem. (and neither do older browsers). Safari doesn’t currently support Sec-Fetch-Mode However, we can accomplish the same effect a different way until browser support is better. It’s not quite as convenient. Since we can’t use to know for certain that a request came in with HTML, let’s swap the logic around and determine whether the request came in with JavaScript. In that case, we’ll respond with the JSON and if not, default to the redirect. Sec-Fetch-Mode We have a few options. Check if the header includes the string . If so, we know the client is explicitly asking for a JSON response. Accept 'application/json' Check if the header is set to . This is not the default value and it’s not a value that can be set with an HTML form, so it’s safe to assume that since the request was sent as JSON and the headers were customized, we can respond with JSON. Content-Type 'application/json' Check for the existence of a custom/proprietary HTTP header like . HTML forms are very limited in what headers they can modify, and they cannot add custom headers like JavaScript can. If we find a custom header, we can assume the request was made with JavaScript. x-custom-fetch We can modify our code to include these checks (you probably don’t need all of them, but I’ll include them anyway). export default defineEventHandler(async (event) => { // Do some work; Get some data const data = { goodBoy: 'Nugget' }; const headers = getRequestHeaders(event); const isJson = headers.accept?.includes('application/json') || headers['content-type']?.includes('application/json') || headers['x-custom-fetch']; if (isJson) { return data; } return sendRedirect(event, String(headers.referer), 303); }); Again, if any of those conditions are met, we know the request must have been created in JavaScript. If that’s true, we respond with JSON. Otherwise, we redirect. Edge Compute Bonus Another little tip I want to share is that this feature we’ve built is actually a perfect candidate for edge compute like . EdgeWorkers Edge compute allows you to add custom logic between the client and the server and intercept or modify requests and/or responses. This means you could get the response from a JSON API and patch this functionality on top. Check if the request was sent with HTML or JavaScript; for HTML, redirect to the ; for JS pass the response through. referer This wouldn’t effect latency like edge compute is known for, but it’s a handy way to add features to an API without modifying the existing API code base. Maybe you don’t work for the company or team responsible for the API. Caveats Now, it’s important for me to point out that all of these for detecting JavaScript requests require checking for headers that are not included by default with a standard HTTP request. So you’ll want to you communicate to developers that they’ll have to include the required headers when constructing HTTP requests in order to receive a JSON response. make sure At least, that should only be the case until browser support gets better and we can rely exclusively on . Sec-Fetch-Mode If you want to see an example of this in practice, I made a video that covers this same topic. https://www.youtube.com/watch?v=E9unSMVzpHM?embedable=true What’s The Fuss Again? Now, you may be thinking to yourself, well, that’s kind of cool, but why should I care? This comes back to the concept of progressive enhancement, which allows developers to build applications that are enhanced to use JavaScript when they can, but if something happens and JavaScript fails or it’s blocked or whatever, it can fall back to using the form to submit a request. However, there is only so much that client-side developers can do. If an API always responds with JSON, then progressive enhancement will make sure the request still works, but the response will be broken. By building APIs that enable progressive enhancement, we enable developers to build progressively enhanced apps. Thank you so much for reading. If you liked this article, please . It's one of the best ways to support me. You can also or if you want to know when new articles are published. share it sign up for my newsletter follow me on Twitter Also published . here