NodeJS WebRTC app using DataChannel isn't working in production server

I do not see the gathering of ice candidates in your code - so it is no surprise your peers cannot establish a connection with each other. Here is the working sample of what your code should look like.

streamer.js:

async function createPeer(configuration) {
  const localCandidates = [];
  // Step 1. Create new RTCPeerConnection
  const peer = new RTCPeerConnection(configuration);
  peer.onconnectionstatechange = (event) => {
    console.log('Connection state:', peer.connectionState);
  };
  peer.onsignalingstatechange = (event) => {
    console.log('Signaling state:', peer.signalingState);
  };
  peer.oniceconnectionstatechange = (event) => {
    console.log('ICE connection state:', peer.iceConnectionState);
  };
  peer.onicegatheringstatechange = (event) => {
    console.log('ICE gathering state:', peer.iceGatheringState);
  };
  // Step 5. Gathering local ICE candidates
  peer.onicecandidate = async (event) => {
    if (event.candidate) {
      localCandidates.push(event.candidate);
      return;
    }
    // Step 6. Send Offer and client candidates to server
    const response = await fetch('/broadcast', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        offer: offer,
        candidates: localCandidates,
      }),
    });
    const {answer, candidates} = await response.json();
    // Step 7. Set remote description with Answer from server
    await peer.setRemoteDescription(answer);
    // Step 8. Add ICE candidates from server
    for (let candidate of candidates) {
      await peer.addIceCandidate(candidate);
    }
  };
  // Step 2. Create new Data channel
  const dataChannel = peer.createDataChannel('host-server');
  dataChannel.onopen = (event) => {
    dataChannel.send('Hello from client!');
  };
  dataChannel.onclose = (event) => {
    console.log('Data channel closed');
  };
  dataChannel.onmessage = (event) => {
    console.log('Data channel message:', event.data);
  };
  // Step 3. Create Offer
  const offer = await peer.createOffer();
  // Step 4. Set local description with Offer from step 3
  await peer.setLocalDescription(offer);
  return peer;
}

const configuration = {
  iceServers: [
    {
      urls: 'stun:global.stun.twilio.com:3478?transport=udp',
    },
  ],
};
// Add turn server to `configuration.iceServers` if needed.
// See more at https://www.twilio.com/docs/stun-turn

createPeer(configuration);

server.js:

const express = require('express');
const bodyParser = require('body-parser');
const webrtc = require('wrtc');

const port = process.env.PORT || 80;
const configuration = {
  iceServers: [
    {
      urls: 'stun:global.stun.twilio.com:3478?transport=udp',
    },
  ],
};
// Add turn server to `configuration.iceServers` if needed.

const app = express();
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.post('/broadcast', async (req, res) => {
  const {offer, candidates} = req.body;
  const localCandidates = [];
  let dataChannel;
  // Step 1. Create new RTCPeerConnection
  const peer = new webrtc.RTCPeerConnection(configuration);
  peer.ondatachannel = (event) => {
    dataChannel = event.channel;
    dataChannel.onopen = (event) => {
      dataChannel.send('Hello from server!');
    };
    dataChannel.onclose = (event) => {
      console.log('Data channel closed');
    };
    dataChannel.onmessage = (event) => {
      console.log('Data channel message:', event.data);
    };
  };
  peer.onconnectionstatechange = (event) => {
    console.log('Connection state:', peer.connectionState);
  };
  peer.onsignalingstatechange = (event) => {
    console.log('Signaling state:', peer.signalingState);
  };
  peer.oniceconnectionstatechange = (event) => {
    console.log('ICE connection state:', peer.iceConnectionState);
  };
  peer.onicegatheringstatechange = (event) => {
    console.log('ICE gathering state:', peer.iceGatheringState);
  };
  peer.onicecandidate = (event) => {
    // Step 6. Gathering local ICE candidates
    if (event.candidate) {
      localCandidates.push(event.candidate);
      return;
    }
    // Step 7. Response with Answer and server candidates
    let payload = {
      answer: peer.localDescription,
      candidates: localCandidates,
    };
    res.json(payload);
  };
  // Step 2. Set remote description with Offer from client
  await peer.setRemoteDescription(offer);
  // Step 3. Create Answer
  let answer = await peer.createAnswer();
  // Step 4. Set local description with Answer from step 3
  await peer.setLocalDescription(answer);
  // Step 5. Add ICE candidates from client
  for (let candidate of candidates) {
    await peer.addIceCandidate(candidate);
  }
});

app.listen(port, () => console.log('Server started on port ' + port));

I found your stun server not fully functional, so I replaced it with another one from Twillio. Also, I added event handlers with which it is easy to track the state of the WebRTC session. You would do well to learn more about WebRTC connection flow, really.