(Node) How do I connect to my API app from my WEB app in Fly?

Hi @danalloway

Good news: I’ve got it working so it will be a case of seeing what I’m doing compared to your app to see what differs.

To test it, I’ve made a web and api Fly app, with Node 17, using Fastify. The web app calls the api app using the .internal hostname and I get a response back from it :rocket:

See https://fastify-web.fly.dev/call-api

(I’ll delete that app at some point, but it’s there as of now)

The files from the two apps to compare to yours - of course it’s pretty basic!

API

package.json

{
  "name": "fastify-api",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "fastify": "^3.27.1"
  }
}

server.js

'use strict'

import Fastify from 'fastify'

const fastify = Fastify({
    logger: true
})

fastify.get('/api', async function (request, reply) {
    return { hello: 'this is a response from the api' }
})

const start = async () => {
    try {
        fastify.log.info('Starting api server ...');
        await fastify.listen(8080, '::')
    } catch (err) {
        fastify.log.error(err)
        process.exit(1)
    }
}
start()

fly.toml

# fly.toml file generated for fastify-api on 2022-02-05T17:38:04Z

app = "fastify-api"


[experimental]
  private_network = true

[[services]]
  internal_port = 8080
  protocol = "tcp"

  [services.concurrency]
    hard_limit = 50
    soft_limit = 25

  [[services.tcp_checks]]
    interval = 5000
    timeout = 2000

Dockerfile

FROM node:17-slim

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY --chown=node:node package.json .
COPY --chown=node:node package-lock.json .

RUN npm ci --only=production

COPY --chown=node:node . .

CMD ["node","server.js"]

… and then the web app calls that api. Its files are as follows …

WEB

package.json

{
  "name": "fastify-web",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "type": "module",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "fastify": "^3.27.1",
    "node-fetch": "^3.2.0"
  }
}

server.js

'use strict'

import Fastify from 'fastify'
import fetch from 'node-fetch';

const fastify = Fastify({
    logger: true
})

fastify.get('/', async (request, reply) => {
    return { hello: 'world' }
})

fastify.get('/call-api', async function (request, reply) {
    try {
        fastify.log.info('Call the api app using fetch ...');
        const results = await fetch('http://fastify-api.internal:8080/api')
        return results.json()
    } catch (err) {
        fastify.log.error(err)
        return { success: false }
    }
})

const start = async () => {
    try {
        fastify.log.info('Starting web server ...');
        await fastify.listen(8080, '::')
    } catch (err) {
        fastify.log.error(err)
        process.exit(1)
    }
}
start()

fly.toml

# fly.toml file generated for fastify-web on 2022-02-05T17:08:46Z

app = "fastify-web"


[experimental]
  private_network = true

[[services]]
  internal_port = 8080
  protocol = "tcp"

  [services.concurrency]
    hard_limit = 50
    soft_limit = 25

  [[services.http_checks]]
    interval = 5000
    method = "get"
    path = "/"
    protocol = "http"
    timeout = 2000
    tls_skip_verify = true

  [[services.ports]]
    handlers = ["tls", "http"]
    port = "443"

  [[services.tcp_checks]]
    interval = 5000
    timeout = 2000

Dockerfile

FROM node:17-slim

USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app

COPY --chown=node:node package.json .
COPY --chown=node:node package-lock.json .

RUN npm ci --only=production

COPY --chown=node:node . .

CMD ["node","server.js"]

So … it could be your fetch needs http as the protocol and the port? For me, it did not work without the port appended (which makes sense, as I guess it defaults to 80 otherwise).

And/or change to listen on ‘::’ rather than 0.0.0.0 (which suggests to me IPv4, not IPv6).

I’m actually not sure if the private networking in my fly.toml is even needed: that may be a legacy of when they first added it, and I’ve left it in ever since. But I have that too.

Finally, if still no luck, you can double-check the private networking is working by adding an extra route in your fastify server to check it resolves e.g

import dns from 'dns';

fastify.get('/test-the-dns', async (request, reply) => {
    let records = [];
    try {
        records = await dns.promises.resolveTxt(`_apps.internal`)
        fastify.log.info(records)
    } catch (err) {
        fastify.log.error(err)
        return { "error": err }
    }

    let appset = records[0][0]
    if (appset == "") return [];

    return appset.split(",");
})

Good luck!

2 Likes