15

This is my project folder

/
  public/
    index.html
    main.js
    adaptor.js
    main.css
  node_modules/
    socket.io/
  index.js

and this is static file configuration in my index.js

app.use(express.static(path.join(__dirname, '/public')));
app.use(express.static(path.join(__dirname, '/node_modules')));
app.get('/', (req, res)=>{
    res.sendFile(path.join(__dirname, 'public', '/index.html'));
})

and this is my index.html

  <script src="/socket.io/socket.io.js" charset="utf-8"></script>
  <script src="/adapter.js" charset="utf-8"></script>
  <script src="/main.js" charset="utf-8"></script>

and this is my nginx configuration

    location / {
            # First attempt to serve request as file, then
            # as directory, then fall back to displaying a 404.
            try_files $uri $uri/ /index.html =404;
            proxy_pass http://localhost:8080;
    }

But I am getting 404 on all the scripts. And an another strange thing is that mime-type on those files is set to text/HTML

What am I doing wrong here?

I have a project, with an identical project structure, and it has the same configuration, but it works for it, and it isn't working in this case.

relentless-coder
  • 1,478
  • 3
  • 20
  • 39
  • yes the `__dirname` is `/home/ubuntu` and nginx `root` is set to `/home/ubuntu` – relentless-coder Oct 11 '17 at 15:44
  • Can you access the static files with /public/myfile? Also, there is no need for Nginx to try to serve index.html -- you node app is taking care of this. – ContinuousLoad Oct 11 '17 at 15:50
  • I deleted my comment about Nginx `root` when I remembered you were proxy passing -- Nginx should not be trying to serve any files at all. You can remove the `try_files` and `root` from the config. – ContinuousLoad Oct 11 '17 at 15:56
  • It would be useful to isolate whether the problem is with Express or nginx. If you make a request directly to the Express server can you access the script files? – skirtle Oct 11 '17 at 16:44

5 Answers5

28

You don't need to configure Nginx and Express to serve static files. Both are capable of doing the job independently, but it is up to you to choose.

For these examples I am assuming the same file structure provided in your question.

In both configurations, load files from / in HTML:

<script src="/main.js"></script> <!-- loads from myapp/public/main.js -->

Express as static file server, Nginx as reverse proxy

express app

const express = require('express');
const app = express();

app.use(express.static('public')); // notice the absence of `__dirname`, explained later on

// if the request URL doesn't match anything in the 'public' folder,
// it will start searching here next.
app.use(express.static('some_other_folder'));

// from my testing, express will automatically locate index.html if
// it is in a static folder. Declaring a route is not required.

/* 
app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
*/

app.listen(8080);

// GET / => index.html
// GET /main.js => main.js

Quick side note: the use of __dirname in express.static() is not required. Under the hood (actually, it's here on line 65) , Express uses the native Node.js path.resolve(). From the docs:

The path.resolve() method resolves a sequence of paths or path segments into an absolute path.

Using path.resolve(__dirname, 'public') actually returns the same as path.resolve('public'). I am thinking that your problem was really telling Nginx to serve static files AND proxy the same requests to Express. OK, on to the rest of my answer.

Nginx configuration

server {
  listen 8081; # must be different port from Express
  server_name example.com;
  location / {
    # hand ALL requests back to express
    proxy_pass http://localhost:8080; 
  }
}

Nginx as static file server, Express as API server

Nginx configuration

server {
  listen 8081;
  server_name example.com;
  location / {
    root /path/to/website/public;
    index index.html;
    try_files $uri $uri/ @express; # instead of 404, proxy back to express using a named location block;
    # source: https://stackoverflow.com/a/15467555/8436941
  }
  location @express {
    proxy_pass http://localhost:8080;
  }
}

Express app

const express = require('express');
const app = express();

// actually, Nginx has already taken care of static files. You can still define other routes for API functions for example.
app.get('/my/api', (req, res) => {/* query database, etc. */});

app.listen(8080);

Hope this helps!

ContinuousLoad
  • 4,692
  • 1
  • 14
  • 19
  • hey, thank you, this worked. I used the first configuration. Could you point me to a documentation or something, because I want to understand what was it that I was doing wrong? Like, how not using `__dirname` affected the solution? and the fact that the same configuration works for my personal project, and so many projects that I have done for the company that I work for. – relentless-coder Oct 11 '17 at 22:57
  • Coming back to this a while later, I think that your original problem was that Nginx was serving from `/home/ubuntu` and not from the public dir. When asked for `main.js` it would then return a 404 and stop there, never proxying anything to Express. As for your other projects, I can't be sure, but I wonder if they worked because your index.html file loaded assets with `src="/public/main.js"`. Anyway, glad it works :) – ContinuousLoad Jun 04 '18 at 12:31
  • 1
    This is the GREATEST ANSWER OF ALL TIME! I was stuck for literally the last 24 hours, barely slept for 3 hours. And then I find this answer. Thank you @ContinuousLoad . None of my routes were accessible, only the main page / was accessible. Now, everything just works fine. Thank you. – bozzmob Sep 06 '18 at 12:17
  • 1
    @bozzmob Thank you, I am humbled. It makes my day to hear about long drawn-out problems being resolved. Definitely been there. Also, I heard the tip about using a named block in `try_files` in [this answer](https://stackoverflow.com/a/15467555/8436941) by Chuan Ma. If you (or anyone) upvoted mine, please upvote that answer too. – ContinuousLoad Sep 07 '18 at 16:16
  • @ContinuousLoad 3 to 4 years later and this answer is still saving lives. I don't think I've seen this clear an explanation of how they handshake, anywhere else. TY! – yen Sep 02 '21 at 19:26
7

Removing the try_files directive from the nginx configuration file solved the issue for me.

Gal Sasson
  • 81
  • 1
  • 1
1
app.use(express.static(path.join(__dirname, '/public')));
app.use(express.static(path.join(__dirname, '/node_modules')));

I think those two lines are the reasons why it raises an error. Can you delete the second one and give it a try? Maybe your express app sets the primary static serving folder "node_modules" and that is why the app cannot serve your main script files.

Also, I don't think it is good idea to serve node_modules directory as static folder. I think in your setup, "bower" seems more appropriate choice as a javascript package manager.

Thanks

supergentle
  • 1,001
  • 1
  • 14
  • 33
  • I am pretty sure the [express docs](https://expressjs.com/en/starter/static-files.html) say this is OK. The first static directories will be searched in order for files. Good point about using a package manager for node_modules. – ContinuousLoad Oct 11 '17 at 16:01
  • 2
    +1 on recommending to not server node_modules. It's a huge directory that can expose tens of thousands of files. Better to only include the front-end libraries you only need somewhere within your "public" folder. – dvsoukup Oct 11 '17 at 16:09
0

Add a . before the / to denote a relative path:

<script src="./socket.io/socket.io.js" charset="utf-8"></script>
<script src="./adapter.js" charset="utf-8"></script>
<script src="./main.js" charset="utf-8"></script>
Sua Morales
  • 894
  • 10
  • 21
0

If you have this structure:

myApp
  app.js
  -public/
     -js/

In app.js you should do like this:

self.app.use(express.static(path.join(__dirname, 'public')));
staticRoutes.forEach(route=> {
            self.app.use(BASEPATH+route,express.static(path.join(__dirname, 'public')));
        });

where staticRoutes is an array like

staticRoutes=[
'services/'
'about/'
];

and in the html (development)

<script type="text/javascript" src="js/util.js"></script>
<a href="/about">About Us</a>

while when in production is safer using the full path + protocol.

loretoparisi
  • 15,724
  • 11
  • 102
  • 146