4

I am setting up an Astro site which will display data fetched from a simple service running on the same host but a different port.

The service is a simple Express app. server.js:

const express = require('express')
const app = express()
const port = 3010

const response = {
  message: "hello"
}
app.get('/api/all', (_req, res) => {
  res.send(JSON.stringify(response))
})

app.listen(port, () => {
  console.log(`listening on port ${port}`)
})

Since the service is running on port 3010, which is different from the Astro site, I configure a server proxy at the Vite level. astro.config.mjs:

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';

export default defineConfig({
  integrations: [react()],
  vite: {
    optimizeDeps: {
      esbuildOptions: {
        define: {
          global: 'globalThis'
        }
      }
    },
    server: {
      proxy: {
        '/api/all': 'http://localhost:3010'
      }
    }
  },
});

Here is where I am trying to invoke the service. index.astro:

---
const response = await fetch('/api/all');
const data = await response.json();
console.log(data);
---

When I run yarn dev I get this console output:

Response {
  size: 0,
  [Symbol(Body internals)]: {
    body: Readable {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      _read: [Function (anonymous)],
      [Symbol(kCapture)]: false
    },
    stream: Readable {
      _readableState: [ReadableState],
      _events: [Object: null prototype],
      _eventsCount: 1,
      _maxListeners: undefined,
      _read: [Function (anonymous)],
      [Symbol(kCapture)]: false
    },
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    type: 'default',
    url: undefined,
    status: 404,
    statusText: '',
    headers: { date: 'Tue, 02 Aug 2022 19:41:02 GMT' },
    counter: undefined,
    highWaterMark: undefined
  }
}

It looks like the network request is returning a 404.

I'm not seeing in the doc much more about server configuration. Am I going about this the right way?

I have this working correctly with a vanilla Vite app and the same config/setup.

How can I proxy local service calls for an Astro application?

Jonathan.Brink
  • 23,757
  • 20
  • 73
  • 115

2 Answers2

1

Short Answer

You cannot proxy service calls with Astro but also you don't have to

For direct resolution answer see section functional test without proxy

Details

  • Astro does not forward the server.proxy config to Vite (unless you patch your own version of Astro), the Astro Vite server config can be seen empty
proxy: {
        // add proxies here
     },

reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L125

  • there is a merge of Astro server with Astro vite.server config but it does not take the proxy param. This is not obvious to get from the code, see tests later.
let result = commonConfig;
    result = vite.mergeConfig(result, settings.config.vite || {});
    result = vite.mergeConfig(result, commandConfig);

reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L167

Tests

Config tests

I tried all possible combinations of how to input config to Astro and in each location a different port number to show which one takes an override

  • a vite.config.js file on root with
export default {
    server: {
      port:6000,
        proxy: {
          '/api': 'http://localhost:4000'
        }
      }
}
  • in two locations in the root file astro.config.mjs
    • server
    • vite.server
export default defineConfig({
  server:{
    port: 3000,
    proxy: {
      '/api': 'http://localhost:4000'
    }
},
  integrations: [int_test()],
  vite: {
    optimizeDeps: {
      esbuildOptions: {
        define: {
          global: 'globalThis'
        }
      }
    },
    server: {
      port:5000,
      proxy: {
        '/api': 'http://localhost:4000'
      }
    }
  }
});
  • in an Astro integration

Astro has a so called integration that helps update the config (sort of Astro plugins) the integration helps identify what was finally kept in the config and also gives a last chance to update the config

integration-test.js

async function config_setup({ updateConfig, config, addPageExtension, command }) {
    green_log(`astro:config:setup> running (${command})`)
    updateConfig({
        server:{proxy : {'/api': 'http://localhost:4000'}},
        vite:{server:{proxy : {'/api': 'http://localhost:4000'}}}
    })
    console.log(config.server)
    console.log(config.vite)
    green_log(`astro:config:setup> end`)
}

this is the output log

 astro:config:setup> running (dev)
{ host: false, port: 3000, streaming: true }
{
  optimizeDeps: { esbuildOptions: { define: [Object] } },
  server: { port: 5000, proxy: { '/api': 'http://localhost:4000' } }
}
 astro:config:setup> end

the proxy parameter is removed from astro server config, the vite config is visible but has no effect as it is overridden, and not forwarded to Vite

test results

  • dev server runs on port 3000 which is from Astro config server all other configs overridden
  • the fetch api fails with the error
 error   Failed to parse URL from /api
  File:
    D:\dev\astro\astro-examples\24_api-proxy\D:\dev\astro\astro-examples\24_api-proxy\src\pages\index.astro:15:20
  Stacktrace:
TypeError: Failed to parse URL from /api
    at Object.fetch (node:internal/deps/undici/undici:11118:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

functional test without proxy

Given that Astro front matter runs on the server side, in SSG mode during build and in SSR mode on page load on the server then the server sends the result html, Astro has access to all host ports and can directly use the service port like this

const response = await fetch('http://localhost:4000/api');
const data = await response.json();
console.log(data);

The code above runs as expected without errors

Reference Example

All tests and files mentioned above are available on the reference example github repo : https://github.com/MicroWebStacks/astro-examples/tree/main/24_api-proxy

wassfila
  • 1,173
  • 8
  • 18
1

You can add your own proxy middleware with the astro:server:setup hook.

For example use http-proxy-middleware in the server setup hook.

// plugins/proxy-middleware.mjs
import { createProxyMiddleware } from "http-proxy-middleware"

export default (context, options) => {
  const apiProxy = createProxyMiddleware(context, options)

  return {
    name: 'proxy',
    hooks: {
      'astro:server:setup': ({ server }) => {
        server.middlewares.use(apiProxy)
      }
    }
  }
}

Usage:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import proxyMiddleware from './plugins/proxy-middleware.mjs';

// https://astro.build/config
export default defineConfig({
  integrations: [
    proxyMiddleware("/api/all", {
      target: "http://localhost:3010",
      changeOrigin: true,
    }),
  ],
});
nwellis
  • 21
  • 3