nuxtjs支持api接口,serverMiddleware

2020-02-22 17:57:05

参考地址 Sending emails through Nuxt.js

Who can’t relate to this: You’ve built a small portfolio page for someone, maybe a company, a friend or yourself. And the only API endpoint you’d need is one for a form. What now? Scaffolding a new service just for this one endpoint?

Fear no more! It’s possible to send emails almost directly through Nuxt but only in SSR mode with a Node.js server running. No more additional API server necessary if you just want to send a mail with data coming from a contact form.

Before diving into the implementation and ideas behind it, here is the full source code I’m referring to through this blog post. It’s from my company’s website developmint.de.

The main goal was a simple API endpoint accessible through Nuxt.js to handle the three contact form fields (nameemail and message) and to send an email with the data if everything is alright.

 Let’s get it on - serverMiddleware

While developing the redirect-module for Nuxt, I came in touch with serverMiddleware. Those run before the actual vue-server-renderer and are usually used for handling static assets, forcing HTTPS or, in case of redirect-module, rewriting routes.

But as they are highly customizable and flexible, why not use them as endpoints instead?

This is possible. An example middleware could look like this:

export default (req, res) => {
    res.write('Hey!')
    res.end()}

When saved to app/test.js for example, you can then add it to your Nuxt config:

export default {
  // ...
  serverMiddleware: [
    { path: '/api/test', handler: '~/api/test' },
  ],
  // ...}

If you rebuild your project in dev mode (yarn run dev) and visit /api/test, you can see Hey! as the page content. Great!

So we can use server middleware to serve content… But there must be a drawback, right?

Right (one could think…)

As Nuxt uses connect as middleware layer (to reduce overhead as it suffices the complexity needed), we are missing some “critical” features in comparison to express.

Besides typical convenience features and routing (which isn’t even mandatory in our case), we can’t get the passed parameters from our req object at the moment. Without those, there is no content for our contact form mail. So what now?

We could use the body-parser package and apply it to the route before we use our custom middleware but then we’d face more “problems” like decoding JSON or setting headers “correctly” sooner or later. Likely it would work from a certain point on but there must be a better way. If we could just use express

 Express in Nuxt.js?

Possibly you have heard it the other way: An express app with Nuxt.js as renderer (like in express-template).

But did you know that you can use express inside a serverMiddleware?

import express from 'express'const app = express()app.post('/', (req, res) => {
    // Validate, sanitize and send})export default {
  path: '/api/contact',
  handler: app}

We declare the express app as the middleware handler and Nuxt is magically gluing everything together.

Now we can save this short snippet under api/contact.js and register our custom server middleware only as path string (because path and handler are inside).

export default {
  // ...
  serverMiddleware: [
    '~/api/contact'
  ],
  // ...}

 Still missing: the mailer!

The last coding part might be less spectacular for everybody who already set up nodemailer in an express app.

Fun fact: Before this implementation I did not as I mostly write backends in Laravel (♥️).

 Inserting the body-parser

Since version 4.16.0, express has its own JSON middleware based on body-parser. To get our JSON parameters out of the POST body, we will need it:

import express from 'express'import nodemailer from 'nodemailer'const app = express()app.use(express.json())// ...

 Validate and sanitize

Now we can get back to our post route. You may wonder why it’s declared as / instead of /api/contact. That’s because our express app’s base route is /api/contact (set through the path export).

import express from 'express'import validator from 'validator'import xssFilters from 'xss-filters'const app = express()app.use(express.json())app.post('/', (req, res) => {
  const attributes = ['name', 'email', 'msg'] // Our three form fields, all required

  // Map each attribute name to the validated and sanitized equivalent (false if validation failed)
  const sanitizedAttributes = attributes.map(n => validateAndSanitize(n, req.body[n]))

  // True if some of the attributes new values are false -> validation failed
  const someInvalid = sanitizedAttributes.some(r => !r)

  if (someInvalid) {
    // Throw a 422 with a neat error message if validation failed
    return res.status(422).json({ 'error': 'Ugh.. That looks unprocessable!' })
  }

  // Upcoming here: sending the mail})

Let’s take a look at the validateAndSanitize function. It could be replaced with another express middleware or plugin but why not writing our own this time:

const rejectFunctions = new Map([
  [ 'name', v => v.length < 4 ],
  [ 'email', v => !validator.isEmail(v) ],
  [ 'msg', v => v.length < 25 ]])const validateAndSanitize = (key, value) => {
  // If map has key and function returns false, return sanitized input. Else, return false
  return rejectFunctions.has(key) && !rejectFunctions.get(key)(value) && xssFilters.inHTMLData(value)}

Each possible attribute receives a rejectFunction that defines in which case the validation will fail. If the function returns false, the validation passed. It looks weird first but I like the reversed approach here because we can avoid a cascade of ifs.

 Send it out

After validating and sanitizing, we are confident that we can send the mail out!



 















import express from 'express'import nodemailer from 'nodemailer'import validator from 'validator'import xssFilters from 'xss-filters'// ...app.post('/', (req, res) => {
  // ...

  if (someInvalid) {
    return res.status(422).json({ 'error': 'Ugh.. That looks unprocessable!' })
  }

  sendMail(...sanitizedAttributes)
  res.status(200).json({ 'message': 'OH YEAH' })})

We use the ES6 spread syntax to pass the sanitized values to the sendMail function:

const sendMail = (name, email, msg) => {
  const transporter = nodemailer.createTransport({
    sendmail: true,
    newline: 'unix',
    path: '/usr/sbin/sendmail'
  })
  transporter.sendMail({
    from: email,
    to: 'support@developmint.de',
    subject: 'New contact form message',
    text: msg  })}

Inside we create a nodemailer transporter and send the email out. We could do this through SMTP, other providers (eg. SES) or (classically) through sendmail as I did. If you want to know more about the setup of nodemailer, here you go.

 Finally we can send emails - A conclusion

So, we did it! If we now send a POST request (eg with axios) through our form, the email will be sent.

Was it worth it? - Definitely! Instead of blocking another port for such a simple API, we can run it together with our Nuxt server (in SSR mode).

Should I adapt my whole API to leverage Nuxt server middleware now? - You could do this, but I would rather not recommend it, as pointed out in a recent article. It’s a great solution for simple and small APIs, but as soon as complexity or the request count increases, better go with own API servers (not only because of performance, also because of better scalability and no “single point of failure”).

 Closing remarks

I hope you enjoyed the article! If so it’d be cool if you could spread the word ☺️

Questions left? Critics? Hit me up on Twitter (@TheAlexLichter) or write me a mail (blog at lichter dot io). I’m curious to hear from you!


  • 2018-02-06 22:23:01

    HTML5 Canvas 的事件处理

    DOM是Web前端领域非常重要的组成部分,不仅在处理HTML元素时会用到DOM,图形编程也同样会用到。比如SVG绘图,各种图形都是以DOM节点的形式插入到页面中,这就意味着可以使用DOM方法对图形进行操作。比如有一个<path id=”p1″>元素,可以直接用jquery增加click事件$(‘#p1’).click(function(){…})”。然而这种DOM处理方法在HTML5的Canvas里不再适用,Canvas使用的是另外一套机制,无论在Canvas上绘制多少图形,Canvas都是一个整体,图形本身实际都是Canvas的一部分,不可单独获取,所以也就无法直接给某个图形增加JavaScript事件。

  • 2018-02-09 01:32:48

    PHP时间戳和日期相互转换

    在php中我们要把时间戳转换日期可以直接使用date函数来实现,如果要把日期转换成时间戳可以使用strtotime()函数实现,下面我来给大家举例说明。

  • 2018-02-09 02:57:46

    如何防止多次打开Activity?

    android:launchMode="singleTask" 加上这句,每次打开如果已有此activity,就会打开原来的实例,否则会创建新的activity​

  • 2018-02-17 18:51:26

    Context都没弄明白,还怎么做Android开发?

    作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言,Activity本质上也是一个对象,那上面的写法有什么问题呢?估计很多人说不清道不明。

  • 2018-02-17 23:33:20

    NestedScrollView+Recyclerview下滑卡顿解决方法

    大家在进行安卓开发用到NestedScrollView+Recyclerview的时候,经常出现的情况就是加载下滑的时候没有任何问题,很流畅,但是在下滑以后明显出现了卡顿的情况,小编根绝这个问题,给大家再来的解决方法,一起来学习下。

  • 2018-02-23 14:15:42

    mysql的取整函数

    ROUND(X) -- 表示将值 X 四舍五入为整数,无小数位 ROUND(X,D) -- 表示将值 X 四舍五入为小数点后 D 位的数值,D为小数点后小数位数。若要保留 X 值小数点左边的 D 位,可将 D 设为负值。

  • 2018-02-23 14:22:50

    mysql的yearweek 和 weekofyear函数

    例如 2010-3-14 ,礼拜天 SELECT YEARWEEK('2010-3-14') 返回 11 SELECT YEARWEEK('2010-3-14',1) 返回 10 其中第二个参数是 mode ,具体指的意思如下: Mode First day of week Range Week 1 is the first week … 0 Sunday 0-53 with a Sunday in this year 1 Monday 0-53 with more than 3 days this year 2 Sunday 1-53 with a Sunday in this year 3 Monday 1-53 with more than 3 days this year 4 Sunday 0-53 with more than 3 days this year 5 Monday 0-53 with a Monday in this year 6 Sunday 1-53 with more than 3 days this year 7 Monday 1-53 with a Monday in this year 2.