Bind Express App to IPv6?

I deployed my Express API to Railway and its talking to Redis and Postgres nicely. My client is built in Next 13 with the App Router and is having trouble discovering
blackjack.railway.internal:8080
blackjack.railway.internal:8080
I think this has to do with my binding the Express app to an IPv6 port or at least making it accessible through IPv6 however after a few hours of searching I'm unable to find a clear way of doing this? client <> server is communicating through ApolloServer.
const logger = getLogger('express');

logger.warn(`Launching in ${process.env.NODE_ENV.toUpperCase()}`);

const schema = makeExecutableSchema({
typeDefs,
resolvers: {
Query,
Mutation,
Subscription,
},
});

const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 20,
});

// Create an Express app and HTTP server; attach both the WebSocket
// server and the ApolloServer to this HTTP server.
const app = express();
const httpServer = createServer(app);

// Create WebSocket server using the HTTP server we just set up.
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});

// Save returned server's info so we can shutdown this server later
const wsServerCleanup = useServer(
{
schema,
context: async (ctx, msg, args) => {
return { pubsub };
},
},
wsServer
);

// Create ApolloServer.
const server = new ApolloServer({
schema,
// just added could cause errors
csrfPrevention: true,
plugins: [
// Proper shutdown for the HTTP server.
ApolloServerPluginDrainHttpServer({ httpServer }),

// Proper shutdown for the WebSocket server.
{
async serverWillStart() {
return {
async drainServer() {
await wsServerCleanup.dispose();
},
};
},
},
],
});

const main = async () => {
// Apollo/GraphQL server start.
await server.start();

if (server) {
logger.info(
`Apollo/GraphQL API is now live on endpoint: ${NODE_PORT}/graphql`
);
} else {
logger.fatal(`Could not start Apollo/GraphQL API`);
process.exit(1);
}

// Middleware for the express application.
app.use(
'/graphql',
cors(),
helmet(),
limiter,
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
return { prisma, pubsub, req };
},
})
);
app.get('/health', (req, res) => {
res.status(200).send('Okay!');
});

const hostname = '::';

// http server start
httpServer.listen(NODE_PORT, hostname, () => {
logger.info(
`Apollo/GraphQL websocket service is live on endpoint: ${NODE_PORT}/graphql`
);
});

setTimeout(() => {
pingRedis();
}, 1000);
};

main();
const logger = getLogger('express');

logger.warn(`Launching in ${process.env.NODE_ENV.toUpperCase()}`);

const schema = makeExecutableSchema({
typeDefs,
resolvers: {
Query,
Mutation,
Subscription,
},
});

const limiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1 minute
max: 20,
});

// Create an Express app and HTTP server; attach both the WebSocket
// server and the ApolloServer to this HTTP server.
const app = express();
const httpServer = createServer(app);

// Create WebSocket server using the HTTP server we just set up.
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
});

// Save returned server's info so we can shutdown this server later
const wsServerCleanup = useServer(
{
schema,
context: async (ctx, msg, args) => {
return { pubsub };
},
},
wsServer
);

// Create ApolloServer.
const server = new ApolloServer({
schema,
// just added could cause errors
csrfPrevention: true,
plugins: [
// Proper shutdown for the HTTP server.
ApolloServerPluginDrainHttpServer({ httpServer }),

// Proper shutdown for the WebSocket server.
{
async serverWillStart() {
return {
async drainServer() {
await wsServerCleanup.dispose();
},
};
},
},
],
});

const main = async () => {
// Apollo/GraphQL server start.
await server.start();

if (server) {
logger.info(
`Apollo/GraphQL API is now live on endpoint: ${NODE_PORT}/graphql`
);
} else {
logger.fatal(`Could not start Apollo/GraphQL API`);
process.exit(1);
}

// Middleware for the express application.
app.use(
'/graphql',
cors(),
helmet(),
limiter,
bodyParser.json(),
expressMiddleware(server, {
context: async ({ req }) => {
return { prisma, pubsub, req };
},
})
);
app.get('/health', (req, res) => {
res.status(200).send('Okay!');
});

const hostname = '::';

// http server start
httpServer.listen(NODE_PORT, hostname, () => {
logger.info(
`Apollo/GraphQL websocket service is live on endpoint: ${NODE_PORT}/graphql`
);
});

setTimeout(() => {
pingRedis();
}, 1000);
};

main();
This is my app.js in my server root. Thanks for any insights.
Solution:
you'd have to use the public address during build and then have your app switch to the private address during runtime
Jump to solution
33 Replies
Percy
Percy12mo ago
Project ID: N/A
bigviking
bigviking12mo ago
N/A
Brody
Brody12mo ago
I must be blind, what is NODE_PORT set to?
bigviking
bigviking12mo ago
8080 Not blind. I left the import statements out
Brody
Brody12mo ago
okay and what error do you get when calling that host:port you gave at the top of your message?
bigviking
bigviking12mo ago
It errors during build. Dockerfile
FROM node:21

WORKDIR /app

COPY package*.json ./
COPY package-lock.json package-lock.json
COPY next.config.js ./

RUN npm ci

COPY . .

RUN npm run build

EXPOSE 3000

CMD [ "npm", "start"]
FROM node:21

WORKDIR /app

COPY package*.json ./
COPY package-lock.json package-lock.json
COPY next.config.js ./

RUN npm ci

COPY . .

RUN npm run build

EXPOSE 3000

CMD [ "npm", "start"]
Error - image
No description
Brody
Brody12mo ago
ah, the private network is not available during build
Solution
Brody
Brody12mo ago
you'd have to use the public address during build and then have your app switch to the private address during runtime
bigviking
bigviking12mo ago
Interesting. Okay I'll give that a shot. Thanks very much. I'm thinking of doing that address switch using start scripts. Would something like this be a good idea?
"build:production" : "export API_URL=public && next build ..."
"build:production" : "export API_URL=public && next build ..."
"start": "export API_URL=private && next start..."
"start": "export API_URL=private && next start..."
Brody
Brody12mo ago
you'd want to use actual environment variables, but yeah I can see that working
bigviking
bigviking12mo ago
Got it. Thank you. I'll see how I might use env vars.
Brody
Brody12mo ago
in your service variables add two new variables
API_PRIVATE_URL
API_PUBLIC_URL
API_PRIVATE_URL
API_PUBLIC_URL
set them accordingly with reference variables https://docs.railway.app/develop/variables#railway-provided-variables specifically using
RAILWAY_PUBLIC_DOMAIN
RAILWAY_PUBLIC_DOMAIN
and
RAILWAY_PRIVATE_DOMAIN
RAILWAY_PRIVATE_DOMAIN
then in your scripts you either do
API_URL=$API_PRIVATE_URL
API_URL=$API_PRIVATE_URL
or
API_URL=$API_PUBLIC_URL
API_URL=$API_PUBLIC_URL
tell me the service name of the backend and I'll write the reference variables for you
bigviking
bigviking12mo ago
Mind if I give it a shot first? It'll help me learn faster.
Brody
Brody12mo ago
absolutely, love that attitude
bigviking
bigviking12mo ago
Oddly, Next is having trouble pulling in env vars from the service on Railway. I'm triaging that now. Vars that appear in next.config.js work but calling process.env.[var] doesn't detect any service vars.
bigviking
bigviking12mo ago
Variables look OK. I am curious as to what Railway "pre-pends" the URLs with. My web socket services starts with "ws://.." and I want to make sure I account for that
No description
Brody
Brody12mo ago
you prefix the reference variables yourself
bigviking
bigviking12mo ago
Perfect
Brody
Brody12mo ago
so lets see your reference variables this is your dockerfile for your next app right?
bigviking
bigviking12mo ago
whoops thats old
Brody
Brody12mo ago
FROM node:21

# turn off extra external api calls
ENV NPM_CONFIG_UPDATE_NOTIFIER false
ENV NPM_CONFIG_FUND false

# needed to bring this variable into the build
ARG API_PUBLIC_URL

WORKDIR /app

COPY next.config.js package*.json ./

RUN npm ci

COPY . ./

RUN npm run build

EXPOSE 3000

CMD [ "npm", "start"]
FROM node:21

# turn off extra external api calls
ENV NPM_CONFIG_UPDATE_NOTIFIER false
ENV NPM_CONFIG_FUND false

# needed to bring this variable into the build
ARG API_PUBLIC_URL

WORKDIR /app

COPY next.config.js package*.json ./

RUN npm ci

COPY . ./

RUN npm run build

EXPOSE 3000

CMD [ "npm", "start"]
show me the raw values for these?
bigviking
bigviking12mo ago
No description
Brody
Brody12mo ago
that referances the same services private and public domain, i thought you wanted to reference a different services public and private domains?
bigviking
bigviking12mo ago
I probably did a bad job of explaining. I have an API built with Express that I want my client (Next) to access. API_PRIVATE_URL and API_PUBLIC_URL above are to connect to the express app from the Next app. Next vars:
No description
bigviking
bigviking12mo ago
Next var raw
No description
Brody
Brody12mo ago
oh okay then you did do it right, you where missing the second screenshot for added context well besides the missing scheme at least its right do this on the next vars
http://${{RAILWAY_PRIVATE_DOMAIN}}
http://${{RAILWAY_PRIVATE_DOMAIN}}
bigviking
bigviking12mo ago
ah.. add the http:// before the ${{....}} ref?
Brody
Brody12mo ago
yep, railway will do the string interpolation
bigviking
bigviking12mo ago
thats neat, let me give it a shot
Brody
Brody12mo ago
youll need to use this dockerfile too
bigviking
bigviking12mo ago
Just updated it, thanks.
Brody
Brody12mo ago
hey just wanting to check back in, did you get this working how you wanted?
bigviking
bigviking12mo ago
Yes, this is answered! Thanks Brody.
Want results from more Discord servers?
Add your server