Categories
Fastify

Add Basic Authentication to Our Fastify App with fastify-basic-auth

With the fastify-basic-auth library, we can add basic authentication to our Fastify app quickly.

In this article, we’ll look at how to use the library to add authentication to our Fastify app.

Installation

We can install the package by running:

npm i fastify-basic-auth

Adding Basic Auth

We can add basic auth to our Fastify app by writing some code.

For example, we can write:

const fastify = require('fastify')({
  logger: true
})

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-basic-auth'), { validate, authenticate })

function validate (username, password, req, reply, done) {
  if (username === 'foo' && password === 'bar') {
    done()
  } else {
    done(new Error('invalid user'))
  }
}

fastify.after(() => {
  fastify.addHook('onRequest', fastify.basicAuth)

  fastify.get('/', (req, reply) => {
    reply.send({ hello: 'world' })
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We register the fastify-basic-auth plugin with validate and authenticate .

validate is a function to validate the username and password.

authenticate is an object to set the realm.

To add basic auth, we called addHook to add a hook that checks the username and password with validate on each request.

Any routes that are registered in the after hook have protection with basic auth.

The validate function can be async.

For example, we can write:

const fastify = require('fastify')({
  logger: true
})

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-basic-auth'), { validate, authenticate })

async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.addHook('onRequest', fastify.basicAuth)

  fastify.get('/', (req, reply) => {
    reply.send({ hello: 'world' })
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

If validate is async, then we don’t need to call done .

Also, we can use it with the onRequest property:

const fastify = require('fastify')({
  logger: true
})

const authenticate = { realm: 'Westeros' }
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.route({
    method: 'GET',
    url: '/',
    onRequest: fastify.basicAuth,
    handler: async (req, reply) => {
      return { hello: 'world' }
    }
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We set fastify.basicAuth as the value of onRequest to add basic auth to our GET / route.

Also, we can use it with fastify-auth :

const fastify = require('fastify')({
  logger: true
})

const authenticate = {realm: 'Westeros'}
fastify.register(require('fastify-auth'))
fastify.register(require('fastify-basic-auth'), { validate, authenticate })
async function validate (username, password, req, reply) {
  if (username !== 'foo' || password !== 'bar') {
    return new Error('invalid user')
  }
}

fastify.after(() => {
  fastify.addHook('preHandler', fastify.auth([fastify.basicAuth]))

fastify.route({
    method: 'GET',
    url: '/',
    onRequest: fastify.auth([fastify.basicAuth]),
    handler: async (req, reply) => {
      return { hello: 'world' }
    }
  })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We register the basic auth handler in the adfter hook and in the onRequest property of the GET / route.

Conclusion

The fastify-basic-auth library lets us add basic auth to our Fastify app with a few lines of code.

Categories
Fastify

Add Authentication to Our Fastify App with fastify-auth

With the fastify-auth library, we can add authentication to our Fastify app quickly.

In this article, we’ll look at how to use the library to add authentication to our Fastify app.

Install

We can install the library by running:

npm i fastify-auth

Add Authentication

We can add authentication to our app by writing:

const fastify = require('fastify')({
  logger: true
})

fastify
  .decorate('verifyJWTandLevel',  (request, reply, done) => {
    done()
  })
  .decorate('verifyUserAndPassword', (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.verifyJWTandLevel,
        fastify.verifyUserAndPassword
      ]),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

fastify.get('/', function (request, reply) {
  reply.send({ hello: 'world' })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We call decorate to add our authentication handlers to our app.

The callback has the request with the request data.

reply lets us set the response.

done is a function we call to call the next middleware.

If there’s an error, we pass in an Error instance to the done function.

We call the register method to register the fasify-auth plugin.

When we make a POST request to the auth-multiple route, we see the request and reply logged.

The routes that are registered in the after callback will have the auth handlers available.

They’re run since we set them to the preHandler property to add the array of auth handlers to run.

The default relationship is an OR relationship.

If we want both handlers to be valid before running the route handler, then we write:

const fastify = require('fastify')({
  logger: true
})

fastify
  .decorate('verifyJWTandLevel',  (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .decorate('verifyUserAndPassword', (request, reply, done) => {
    console.log(request, reply)
    done()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.verifyJWTandLevel,
        fastify.verifyUserAndPassword
      ], {
        relation: 'and'
      }),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

fastify.get('/', function (request, reply) {
  reply.send({ hello: 'world' })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

Fastify-auth supports promises returned by the functions.

So we can use async and await in our auth handler callbacks:

const fastify = require('fastify')({
  logger: true
})

fastify
  .decorate('asyncVerifyJWTandLevel', async function (request, reply) {
    console.log('asyncVerifyJWTandLevel');
    await validation()
  })
  .decorate('asyncVerifyUserAndPassword', function (request, reply) {
    console.log('asyncVerifyUserAndPassword');
    return Promise.resolve()
  })
  .register(require('fastify-auth'))
  .after(() => {
    fastify.route({
      method: 'POST',
      url: '/auth-multiple',
      preHandler: fastify.auth([
        fastify.asyncVerifyJWTandLevel,
        fastify.asyncVerifyUserAndPassword
      ]),
      handler: (req, reply) => {
        req.log.info('Auth route')
        reply.send({ hello: 'world' })
      }
    })
  })

fastify.get('/', function (request, reply) {
  reply.send({ hello: 'world' })
})

fastify.listen(3000, '0.0.0.0',  function (err, address) {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`server listening on ${address}`)
})

We use async and await with the asyncVerifyJWTandLevel handler.

And we return a promise within the asyncVerifyUserAndPassword handler.

Conclusion

We can add our own auth logic easily to our Fastify app with the fastify-auth plugin.

Categories
Express

Document Our Express API with the Swagger UI Express Library

Documenting APIs is painful and tedious.

However, we can make our lives easier with the Swagger UI Express library if we’re using Express.

In this article, we’ll look at how to use the library to document our Express API.

Installation

We can install the package by running:

npm i swagger-ui-express

Documenting Our Endpoints

We can document our endpoints by writing some code.

First, we add our swagger.json file by writing:

{
  "swagger": "2.0",
  "info": {
    "title": "some-app",
    "version": "Unknown"
  },
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
    "/{id}": {
      "parameters": [
        {
          "name": "id",
          "required": true,
          "in": "path",
          "type": "string",
          "description": "some id"
        }
      ],
      "get": {
        "operationId": "routeWithId",
        "summary": "some route",
        "description": "some route",
        "produces": [
          "application/json"
        ],
        "responses": {
          "200": {
            "description": "200 response",
            "examples": {
              "application/json": "{ foo: 1 }"
            }
          }
        }
      }
    },
    "/": {
      "parameters": [],
      "get": {
        "operationId": "anotherRoute",
        "summary": "another route",
        "description": "another route",
        "produces": [
          "application/json"
        ],
        "responses": {
          "202": {
            "description": "202 response",
            "examples": {
              "application/json": "{ foo: 'bar' }"
            }
          }
        }
      }
    }
  }
}

It has all the information about the endpoints in our app.

The data includes the parameters required and the responses.

parameters has the parameters and responses has possible responses from the endpoint.

Then in our app file, we write:

index.js

const express = require('express');
const bodyParser = require('body-parser');
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
const swaggerOptions = {
  swaggerOptions: {
    validatorUrl: null
  }
};

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, swaggerOptions));

app.get('/', (req, res) => {
  res.json({ foo: 'bar' });
});

app.get('/:id', (req, res) => {
  res.json({ foo: req.params.id });
});

app.listen(3000, () => console.log('server started'));

We require our swagger.json file and add the api-docs endpoint to show the document.

validatorUrl has the URL for Swagger’s validator for validating our document.

We just add:

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, swaggerOptions));

to add the endpoint and pass in the options for displaying the document.

The swaggerUi.setup method takes the document and options as arguments.

When we go to the /api-docs endpoint, we should see the Swagger interface and try requests with our app.

We can enter the ID for the /{id} endpoint and see the response after clicking Execute.

Custom CSS Styles

We can add custom CSS with the customCss property:

const express = require('express');
const bodyParser = require('body-parser');
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
const swaggerOptions = {
  swaggerOptions: {
    validatorUrl: null,
  },
  customCss: '.swagger-ui .topbar { display: none }'
};

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, swaggerOptions));

app.get('/', (req, res) => {
  res.json({ foo: 'bar' });
});

app.get('/:id', (req, res) => {
  res.json({ foo: req.params.id });
});

app.listen(3000, () => console.log('server started'));

We hid the top bar with:

customCss: '.swagger-ui .topbar { display: none }'

Load Swagger from URL

We can load the Swagger file from a URL with the url option:

const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');

const options = {
  swaggerOptions: {
    url: 'http://petstore.swagger.io/v2/swagger.json'
  }
}

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(null, options));

We can load more than one file:

const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');

const options = {
  explorer: true,
  swaggerOptions: {
    urls: [
      {
        url: 'http://petstore.swagger.io/v2/swagger.json',
        name: 'Spec1'
      },
      {
        url: 'http://petstore.swagger.io/v2/swagger.json',
        name: 'Spec2'
      }
    ]
  }
}

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(null, options));

Conclusion

We can document our Express API easily with the Swagger UI Express library.

Categories
Express Nodejs Vue 3

Create a Todo App with Vue 3, Express and Sematext

Vue 3 is the up and coming version of the popular Vue front end framework.

We can pair that we the back end of our choice to create an app that we want to create.

In this article, we’ll create a Vue 3 front end that’s paired with an Express back end that uses Sematext for logging.

Get Started

We can start by creating our scaffold.

First, we create a project folder with a backend and frontend folders inside.

Then we can go into our backend folder and create our Express app.

We can use the Express generator package to make this easy.

To run it, we run:

npx express-generator

in our backend folder to add the files.

Then we run:

npm i

to install the packages.

Next, we create our Vue 3 front end project.

To do that, we go into the project folder root and run:

npm init vite-app frontend

This will create the project files in the frontend folder and install the required packages.

Backend

Now we have the scaffolding for both apps, we can work on the back end app.

We install a few more packages that we’ll need for our back end.

To do that we run:

npm i cors dotenv sematext-agent-express sqlite3

cors is a package to let us communicate between front end and back end regardless of the domain they’re in.

dotenv lets us read the environment variables.

sematext-agent-express is the Sematext package for Express apps.

sqlite3 lets us save data to a SQLite database.

Next, we create a todo.js file in the routes folder.

And then in app.js, we change the existing code to:

require('dotenv').config()
const { stHttpLoggerMiddleware } = require('sematext-agent-express')
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var cors = require('cors');

var indexRouter = require('./routes/index');
var todosRouter = require('./routes/todo');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(cors())

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(stHttpLoggerMiddleware)

app.use('/', indexRouter);
app.use('/todos', todosRouter);



// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

We added the todosRouter middleware to let us use the todos route file.

And we have:

app.use('/todos', todosRouter);

to use the todosRouter file.

Also, we have:

require('dotenv').config()

to read the .env file which we’ll need for reading the API token for Sematext.

We add the Sematext Express middleware into our app with:

app.use(stHttpLoggerMiddleware)

so we can use its logging capabilities in our app.

We also have:

var cors = require('cors');

to let us do cross-domain communication.

Next, we go to todo.js and replace the existing code with:

var express = require('express');
var sqlite3 = require('sqlite3').verbose();
const { stLogger } = require('sematext-agent-express')

var db = new sqlite3.Database('./todos.db');
var router = express.Router();

router.get('/', (req, res) => {
  db.serialize(() => {
    db.all("SELECT * FROM todos", (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.post('/', (req, res) => {
  const { name } = req.body;

  db.serialize(() => {
    db.run("INSERT INTO todos (name) VALUES (?)", name, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

router.put('/:id', (req, res) => {
  const { id } = req.params;
  const { name } = req.body;

  db.serialize(() => {
    db.run("UPDATE todos SET name = ? WHERE id = ?", name, id, (err, result) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(result);
      res.json(result);
    });
  });
});

router.delete('/:id', (req, res) => {
  const { id } = req.params;
  db.serialize(() => {
    db.run("DELETE FROM todos WHERE id = ?", id, (err, results) => {
      if (err) {
        stLogger.error(err);
        return res.status(500).send(err);
      }
      stLogger.info(results);
      res.json(results);
    });
  });
});

module.exports = router;

We add the routes with the router methods.

We use the stLogger object from the sematext-agent-express package to let us do the logging.

In each route, we have the stLogger.error method to log errors.

And we have stLogger.info to log other information like the database results.

Each time the route middleware runs, we’ll see something logged.

If there’s an error we return that as the response back to the client with the if statements.

We call db.serialize to run database queries in sequence.

And we use db.run to run INSERT, UPDATE, DELETE statements.

To run SELECT queries, we run db.all to get all the rows.

We use parameterized queries so that values are escaped before we run the queries.

req.params gets the request parameters from the URL.

req.body gets the request body.

We have:

var db = new sqlite3.Database('./todos.db');

The todos.db file will be created if it doesn’t exist.

Once it’s created we can open the file by using the SQLite browser from https://sqlitebrowser.org/.

We can download the file and install it.

Then we can open the todos.db file and run:

CREATE TABLE todos (id INTEGER PRIMARY KEY, name TEXT NOT NULL)

to create the todos table so we can write to it.

Now the SQL statements in our code should run properly.

Then we go into the backend folder and create the .env file.

And then we add the LOGS_TOKEN key so that we can use Sematext for logging:

LOGS_TOKEN=YOUR_OWN_KEY_FROM_SEMATEXT

We can get the key by signing up for an account by going to https://apps.sematext.com/ui/login/.

Once we’re in, we can click on Apps on the left menu.

Then click on New App on the top right to create our app.

Once you see the app entry, we click on the menu button on the right side of the row, and click Integrations.

Then we see Node.js on the left side of what’s open and follow the instructions from there.

Front End

Now that the back end is done, we can move onto the front end.

First, we install the axios HTTP client so that we can make HTTP requests.

We can do that by running:

npm i axios

We go to the frontend/components folder and create a Todo.vue file.

Then we add:

<template>
  <form @submit.prevent="save">
    <input type="text" v-model="todo.name" />
    <input type="submit" value="save" />
    <button type="button" @click="deleteTodo" v-if='todo.id'>delete</button>
  </form>
</template>

<script>
import axios from "axios";
const APIURL = "http://localhost:3000";

export default {
  name: "Todo",
  props: {
    todo: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      name: "",
    };
  },
  methods: {
    async save() {
      const { name } = this.todo;
      if (this.todo.id) {
        await axios.put(`${APIURL}/todos/${this.todo.id}`, { name });
      } else {
        await axios.post(`${APIURL}/todos`, { name });
      }
      this.$emit("saved-todo");
    },

    async deleteTodo() {
      const { data } = await axios.delete(`${APIURL}/todos/${this.todo.id}`);
      this.$emit("saved-todo");
    },
  },
};
</script>

to it.

We use the form to add or edit the files.

Also, we have a delete button to delete the todos.

The save method makes a PUT request to our back end if the todo has an id.

This mneans it’s an existing entry, so we make a PUT request.

Otherwise, we make a POST request.

Once they’re successful, then we emit the save-todos event so that we can get the latest data later.

Also, we have the deleteTodo method so that we can make a DELETE request to delete a item.

The Todo component takes a todo prop, which is optional.

We have the default method to return the default value for it.

The v-if in the template checks if the todo.id exists.

If it does, then it’s displayed so we can call delete to delete the todo item.

We also have a @subnmit.prevent directive to let us submit our form and run the save method.

Next we work on the App.vue file.

We write:

<template>
  <div>
    <Todo @saved-todo="getTodos"></Todo>
    <Todo @saved-todo="getTodos" v-for="t of todos" :todo="t" :key="t.id"></Todo>
  </div>
</template>

<script>
import axios from "axios";
import Todo from "./components/Todo.vue";
const APIURL = "http://localhost:3000";

export default {
  name: "App",
  components: {
    Todo,
  },
  data() {
    return {
      todos: [],
    };
  },
  beforeMount() {
    this.getTodos();
  },
  methods: {
    async getTodos() {
      const { data } = await axios.get(`${APIURL}/todos`);
      this.todos = data;
    },
  },
};
</script>

We add the Todo component that we created earlier.

The first one is for adding the todo item.

The getTodos method lets us get the todo items.

It’s run when the saved-todo event is emitted.

We listen to the event with @saved-todo on the template.

Also, we call getTodos in the beforeMount hook so that we can get the todos when the page loads.

Running Our App

Once this is done, we can run our app.

We first go into the backend folder and run:

npm start

Then we go into the frontend folder and run:

npm run dev

Once that’s done, we can go to http://localhost:3001 to see the app.

Now we should see:

when we go to http://localhost:3001 and when we do something in our app, we see something like:

logged in Sematext.

It’ll log your activities.

Conclusion

Using Sematext with an Express app is easy.

We just use the sematext-express-agent package to let us log with it.

Creating a Vue front end is also easy.

Vue 3 is the up and coming version of Vue. It’s almost ready for production.

Categories
Restify

Restify — Error Handling with Restify-Errors

Restify is a simple Node back end framework.

In this article, we’ll look at how to format error messages with Restify.

Formatting Errors

We can format errors with the error handler.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

var server = restify.createServer();

server.get('/hello/:foo', function(req, res, next) {
  var err = new errors.InternalServerError('not found');
  return next(err);
});

server.on('InternalServer', function (req, res, err, cb) {
  err.toString = function() {
    return 'an internal server error occurred!';
  };

  err.toJSON = function() {
    return {
      message: 'an internal server error occurred!',
      code: 'error'
    }
  };

  return cb();
});

server.on('restifyError', function (req, res, err, cb) {
  return cb();
});

server.listen(8080);

We have the InternalServer handler with the toString and toJSON methods to format string and JSON errors.

The restifyError error handler has the error handler.

Also, we can create formatters for other content types.

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer({
  formatters: {
    ['text/html'](req, res, body) {
      if (body instanceof Error) {
        return `<html><body>${body.message}</body></html>`;
      }
    }
  }
});

server.get('/', function(req, res, next) {
  res.header('content-type', 'text/html');
  return next(new errors.InternalServerError('error!'));
});

server.listen(8080);

to create a content formatter for the text/html content type.

restify-errors

The restify-errors module exposes a suite of error constrictors for many common HTTP requests and REST related errors.

For example, we can create errors by writing:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  return next(new errors.ConflictError("conflict"));
});

server.listen(8080);

Then when we go to http://localhost:8080 , we get:

{"code":"Conflict","message":"conflict"}

We can also pass in Restify errors as an argument of res.send .

For example, we can write:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  res.send(new errors.GoneError('gone'));
  return next();
});

server.listen(8080);

We pass in the GoneError instance into the res.send method.

Then when we go to http://localhost:8080 , we get:

{"code":"Gone","message":"gone girl"}

The automatic serialization to JSON is done by calling JSON.stringify on the Error object.

They all have the toJSON method defined.

If the object doesn’t have the toJSON method defined, then we would get an empty object.

HttpError

HttpError is a generic HTTP error that we can return with the statusCode and body properties.

The statusCode will be automatically set with the HTTP status code, and the body will be set to the message by default.

All status codes between 400 error 500s are automatically be converted to an HttpError with the name being in PascalCase and spaces removed.

RestError

Restify provides us with built it errors with the code and message properties/

For example, if we have:

var restify = require('restify');
var errors = require('restify-errors');

const server = restify.createServer();

server.get('/', function(req, res, next) {
  return next(new errors.InvalidArgumentError("error"));
});

server.listen(8080);

Any 400 or 500 series errors can be created with restify-errors .

Conclusion

We can create error objects with restify-errors .

They’ll be automatically be serialized into JSON if we use it in our response.