Categories
Express

How to Let Users Download a File from Node.js Server with Express?

Sometimes, we want to let users download files within our Express app.

In this article, we’ll look at how to let users download files from Express apps.

res.download

One way to let users download a file from a Node.js server is to use the res.download method available with Express.

For instance, we can write:

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

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.get('/download', (req, res) => {
  const file = `${__dirname}/download/foo.txt`;
  res.download(file);
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

We just specify the path to the file with the file string variable.

Then we pass that to the res.download method to download the file.

__dirname is the current working directory of the Express app.

Serve Files as Static Files

We can also use the express.static middleware to serve a folder in our server as a static files folder.

To do this, we write:

const express = require('express')
const app = express()
const port = 3000
app.use('/download', express.static('download'))

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

We call app.use to use the Express static middleware.

The first argument is the path that the user uses to access the static folder’s content.

And the 2nd argument is the Express static middleware.

We create the middleware by calling express.static with the folder path string.

Therefore, we serve the download folder in our server as the static folder.

res.attachment

We can also return a file as a response in our route with the route.attachment method.

It takes the same argument as the res.download method.

It sets the Content-Disposition response header to attachment .

And it also sets the Content-Type response header according to the file type being downloaded.

For instance, we can write:

const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.get('/download', (req, res) => {
  const file = `${__dirname}/download/foo.txt`;
  res.attachment(file).send();
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

We call res.attachment with send to send the file download response to the user.

Conclusion

There’re several ways we can use to let users download files to user’s device via an Express endpoint.

Categories
Express

Error Handling with Express

Like with any other apps, we have to make Express apps ready to handle errors like unexpected inputs or file errors.

In this article, we’ll look at how to handle errors with Express.

Catching Errors

Error handling is the process of processing any errors that comes up both synchronously and asynchronously. Express comes with a default error handler so that we don’t have to write our own.

For example, if we throw errors in our route handlers as follows:

app.get('/', (req, res, next) => {
  throw new Error('error');
});

Express will catch it and proceed. We should see error instead of the app crashing.

For asynchronous errors, we have to call next to pass the error to Express as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

The code above will throw an error in the setTimeout callback and the catch block has the next call with the error passed in to call the built-in error handler.

We should see error instead of the app crashing.

Likewise, we have to catch rejected promises. We can do it as follows:

app.get('/', (req, res, next) => {
  Promise
    .reject('error')
    .catch(next)
});

Or with the async and await syntax, we can write the following:

app.get('/', async (req, res, next) => {
  try {
    await Promise.reject('error');
  }
  catch (ex) {
    next(ex);
  }
});

We should see error displayed instead of the app crashing with the stack trace.

The same logic also applies to routes with a chain of event handlers. We can call next as follows:

app.get('/', [
  (req, res, next) => {
    setTimeout(() => {
      try {
        throw new Error('error');
      }
      catch (ex) {
        next(ex);
      }
    })
  },
  (req, res) => {
    res.send('foo');
  }
]);

We should see the error displayed with the stack trace.

Default Error Handler

The default error handler catches the error when we call next and don’t handle it with a custom error handler.

The stack trace isn’t displayed in production environments.

If we want to send a different response than the default, we have to write our own error handler.

The only difference between route handlers, middleware and error handlers is that error handler has the err parameter before the request parameter that contains error data.

We can write a simple route with a custom event handler as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

app.use((err, req, res, next) => {
  res.status(500).send('Error!')
})

Note that we have the error handler below the route. The order is important. It has to below all the routes that we want to handle with it so that the error handler will get called.

We can write more than one custom error handler as follows:

app.get('/', (req, res, next) => {
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

app.use((err, req, res, next) => {
  if (req.foo) {
    res.status(500).send('Fail!');
  }
  else {
    next(err);
  }
})

app.use((err, req, res, next) => {
  res.status(500).send('Error!')
})

What we have above is that if req.xhr is truthy in the first error handler, then it’ll send the Fail! response and not proceed to the second one. Otherwise, the second one will be called by calling next .

So if we add req.foo = true before the setTimeout in our route handler to have:

app.get('/', (req, res, next) => {
  req.foo = true;
  setTimeout(() => {
    try {
      throw new Error('error');
    }
    catch (ex) {
      next(ex);
    }
  })
});

Then we get Fail! . Otherwise, we get Error! .

Calling next will skip to the error handler even if there’re other route handlers in the chain.

Conclusion

To handle errors, we should call next to delegate the error handling to the default event handler if no custom event handler is defined.

We can also define our own error handler function by creating a function that has the err parameter before, req , res , and next . The err parameter has the error object passed from next .

Error handlers have to be placed after all the regular route handling code so that they’ll get run.

Also, we can have multiple error handlers. If we call next on it, then it’ll proceed to the next error handler.

Categories
Hapi

Server-Side Development with Hapi.js — Sessions and Advanced HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Advanced HTTP Requests

We can make complex HTTP requests with the @hapi/wreck module.

For example, we can write:

const Wreck = require('@hapi/wreck');
const Https = require('https')
const Http = require('http')

const method = 'GET';
const uri = '/';
const readableStream = Wreck.toReadableStream('foo=bar');

const wreck = Wreck.defaults({
  headers: { 'x-foo-bar': 123 },
  agents: {
    https: new Https.Agent({ maxSockets: 100 }),
    http: new Http.Agent({ maxSockets: 1000 }),
    httpsAllowUnauthorized: new Https.Agent({ maxSockets: 100, rejectUnauthorized: false })
  }
});

const wreckWithTimeout = wreck.defaults({
  timeout: 5
});

const options = {
  baseUrl: 'https://www.example.com',
  payload: readableStream || 'foo=bar' || Buffer.from('foo=bar'),
  headers: {},
  redirects: 3,
  beforeRedirect: (redirectMethod, statusCode, location, resHeaders, redirectOptions, next) => next(),
  redirected: function (statusCode, location, req) {},
  timeout: 1000,
  maxBytes: 1048576,
  rejectUnauthorized: false,
  agent: null,
};

const example = async () => {
  const promise = wreck.request(method, uri, options);
  try {
    const res = await promise;
    const body = await Wreck.read(res, options);
    console.log(body.toString());
  }
  catch (err) {
    console.log(err)
  }
};

example()

The headers property has the request headers.

timeout has the request timeout in milliseconds.

maxBytes has the max size of a request.

agent has the user agent.

We can add the secureProtocol is the SSL method to use.

And the ciphers property can be added to choose the TLS cipher.

Then we use Wreck.read to read the response.

We can also call promise.req.abort() to abort the request.

Session Handling

We can handle session data with the @hapi/yar module.

For instance, we can write:

const Hapi = require('@hapi/hapi');

const server = Hapi.Server({ port: 3000 });
const options = {
  storeBlank: false,
  cookieOptions: {
    password: 'the-password-must-be-at-least-32-characters-long'
  }
};

const init = async () => {
  await server.register({
    plugin: require('@hapi/yar'),
    options
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      request.yar.set('example', { key: 'value' });
      return 'hello';
    }
  });

  server.route({
    method: 'GET',
    path: '/session',
    handler: (request, h) => {
      const example = request.yar.get('example');
      return example;
    }
  });

  await server.start();
  console.log('Server running at:', server.info.uri);
};
init();

to register the @hapi/yar plugin with:

await server.register({
  plugin: require('@hapi/yar'),
  options
});

The options property has the cookieOptions.password property to set the password for encrypting the session.

Then in the / route, we call request.yar.set method to add the key and value to the session.

And then in the /session route, we call request.yar.get method with the key we want to get to get the data.

We should get:

{
  "key": "value"
}

as the response.

Conclusion

We can use the @hapi/wreck module to make advanced HTTP requests.

And the @hapi/yar module lets us manage session data across our app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates and HTTP Requests

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render Nunjucks templates in our Hapi app with the @hapi/vision plugin.

To do this, we write:

index.js

const Nunjucks = require('nunjucks');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

server.views({
    engines: {
      html: {
        compile: (src, options) => {
          const template = Nunjucks.compile(src, options.environment);
          return (context) => {
            return template.render(context);
          };
        },
        prepare: (options, next) => {
          options.compileOptions.environment = Nunjucks.configure(options.path, { watch : false });
          return next();
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

server.route({ method: 'GET', path: '/', handler: rootHandler });

await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We require the Nunjucks module.

Then we call Nunjucks.compile to with the src parameter to compile the source to the template.

Then we return a function that calls template.render with the context to render the template.

The context has the 2nd argument of h.view as its value.

The prepare method is called before the compile method and it sets the environment option set environment-specific settings.

index.html has the template content.

@hapi/vision also supports render Twig templates.

To do this, we write:

const Twig = require('twig');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      twig: {
        compile: (src, options) => {
          const template = Twig.twig({ id: options.filename, data: src });
          return (context) => {
            return template.render(context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.twig

{{ message }}

We set the engines.twig.compile property to a function.

In it, we call Twig.twig to set the id and data of the template.

And we return a function that takes the context parameter and call template.render on it.

context has the object that we pass in as the 2nd argument.

index.twig is our Twig template.

Making HTTP Requests

We can make HTTP requests in our Node app with the @hapi/wreck module.

For instance, we can write:

const Wreck = require('@hapi/wreck');

const makeRequest = async () => {
  const { res, payload } = await Wreck.get('http://example.com');
  console.log(payload.toString());
};

try {
  makeRequest();
}
catch (ex) {
  console.error(ex);
}

We require the @hapi/wreck module.

Then we call Wreck.get to make a GET request.

Then we get the response with the payload property.

And we convert that to a string with the toString method.

Conclusion

We can render various kinds of templates with the @hapi/vision module.

Also, we can make HTTP requests with the @hapi/wreck module in our Node app.

Categories
Hapi

Server-Side Development with Hapi.js — Rendering Templates

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Rendering Templates

We can render templates with the @hapi/vision module.

For instance, we can write:

index.js

const Ejs = require('ejs');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello Ejs!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { ejs: Ejs },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.ejs

<%= message %>

We have await server.register(Vision); to register the @hapi/vision plugin.

Then we call h.view to render the view.

title and message will be available in the template.

We set the engines.ejs property to the Ejs module to use it as our rendering engine.

We can use the Handlebars view engine by writing:

index.js

const Handlebars = require('handlebars');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { html: Handlebars },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html

{{message}}

We change the engines property to { html: Handlebars } and require the handlebars module to use the Handlebars views engine.

To use the Pug view engine with Hapi, we write:

index.js

const Pug = require('pug');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: { pug: Pug },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.pug

p #{message}

To use the Mustache view engine, we write:

const Mustache = require('mustache');
const Hapi = require('@hapi/hapi');
const Vision = require('@hapi/vision');

const server = Hapi.Server({ port: 3000 });

const rootHandler = (request, h) => {
  return h.view('index', {
    title: request.server.version,
    message: 'Hello!'
  });
};

const init = async () => {
  await server.register(Vision);

  server.views({
    engines: {
      html: {
        compile: (template) => {
          Mustache.parse(template);
          return (context) => {
            return Mustache.render(template, context);
          };
        }
      }
    },
    relativeTo: __dirname,
    path: 'templates'
  });

  server.route({ method: 'GET', path: '/', handler: rootHandler });

  await server.start();
  console.log('Server running at:', server.info.uri);
};

init();

templates/index.html :

{{message}}

We have the engines.html.compile method to compile the template into rendered HTML.

context has the data we pass into the 2nd argument of h.view .

Conclusion

We can render various kinds of templates into HTML with the @hapi/vision plugin.