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!


  • 2020-04-17 11:28:35

    TweenMax中文初级教程二

    TimelineMax是GreenSock 动画平台中的动画组织、排序、管理工具,可创建时间轴(timeline)作为动画或其他时间轴的容器,这使得整个动画控制和精确管理时间变得简单,避免了通过反复delay和回调进行动画。 作者:李霖弢 链接:https://www.jianshu.com/p/8c0361e43bf5 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 2020-04-17 11:28:57

    TweenMax中文初级教程三

    动画关键词:CSS(一般可以省略) CSSPlugin用于在TweenMax中对DOM元素的CSS相关属性进行动画 在CSSPlugin中CSS属性需要写成驼峰式,例如font-size应当写作fontSize。有时候你可以在一些默认px为单位的属性中省略单位,CSSPlugin还可以在不同的单位间做动画:

  • 2020-04-17 11:29:23

    TweenMax中文初级教程四

    用于滚动窗口(类似于window.scrollTo(x, y))或DOM元素(如myDiv.scrollTop = y; myDiv.scrollLeft = x;)。滚动窗口时使用window作为动画目标。

  • 2020-04-17 14:06:29

    图片解释EaseIn,EaseOut,EaseInOut

    1.EaseIn:即缓动发生在入口处,也就是刚开始的时候。 2.EaseOut:即缓动发生在出口处,也就是结束之前。 3.EaseInOut:就是两边都有缓动了.

  • 2020-04-21 14:47:13

    Redis危险命令重命名、禁用

    flushdb,清空数据库 flushall,清空所有记录,数据库 config,客户端连接后可配置服务器 keys,客户端连接后可查看所有存在的键

  • 2020-04-21 15:13:15

    redis 简单使用

    Redis和Memcached类似,也属于k-v数据存储 Redis官网 https://redis.io支持更多value类型,除了和string外,还支持hash、lists(链表)、sets(集合)和sorted sets(有序集合) Redis是可以把数据存储在磁盘上的并且使用了两种文件格式:全量数据(RDB)和增量请求(aof)。一般叫做redis持久化 全量数据格式是把内存中的数据写入磁盘,便于下次读取文件进行加载。

  • 2020-04-21 15:14:20

    SpringBoot + Redis:基本配置及使用

    # Redis数据库索引(默认为0) spring.redis.database=0# Redis服务器地址 spring.redis.host=127.0.0.1# Redis服务器连接端口 spring.redis.port=6379# Redis服务器连接密码(默认为空) spring.redis.password=# 连接池最大连接数(使用负值表示没有限制) spring.redis.jedis.pool.max-active=20# 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.jedis.pool.max-wait=-1# 连接池中的最大空闲连接 spring.redis.jedis.pool.max-idle=10# 连接池中的最小空闲连接 spring.redis.jedis.pool.min-idle=0# 连接超时时间(毫秒) spring.redis.timeout=1000