Build a Fullstack app with Adonis and React
Tags: webdev, react, adonisjs, node
Published: 2021-02-17 19:07:12

---
title: Build a Fullstack app with Adonis and React
published: true
description: Combining Adonis, React and Tailwindcss together under a single monorepo codebase.
tags: webdev, react, adonisjs, node
canonical_url: https://magitek.dev/articles/2021-02-17-build-a-fullstack-app-with-adonis-and-react/
cover_image: https://cdn-images-1.medium.com/max/800/1*2qs-kdxldLaOT6UiDhjt1g.png
---

Inspired by Amir Hosein Samili's [post](https://dev.to/amirhoseinsalimi/integrate-vue-js-and-adonis-js-v5-using-laravel-mix-p0i) on managing Adonis and Vue as a monorepo, I want to show you how we can replicate this approach with React. As a bonus, we'll add in Tailwind as our CSS framework, but you can choose whatever styling methodology or framework that floats your boat. The goal is to let both our apps live together and allow the server pass the routing  and styles down to the client.

Please note we'll be following the same flow as the referenced article, but making some changes and additions to meet our React/Tailwind needs. Please share your thanks and follow there as well! 

## Create Initial Project

We'll start with creating our Adonis project using the latest version (v5). If you are unfamiliar with Adonis, take some time and look over the docs at preview.adonisjs.com. When running the starting command below, make sure to select "Web Application" since we will be making use of both backend and frontend routing in a single environment. I also say yes to eslint and prettier during the cli instructions and then customize them to my own personal preferences.

```node
yarn create adonis-ts-app <app-name>;

cd <app-name>;
```

## Setup Asset Bundler

With our project compiler ready, we now need to configure the server to be aware of and compile our React assets that we'll be using for the frontend.

```node
yarn add adonis-mix-asset && yarn add -D laravel-mix laravel-mix-tailwind;
```

The invoke command will setup the providers, commands and webpack.mix.js we need to resolve and build the relationship between our backend and frontend.

```node
node ace invoke adonis-mix-asset;
```

Since we are having Adonis and React in the same monorepo and will be letting this repo manage our React app through adonis-mix-asset (Laravel Mix), we need to have some extra webpack configurations for hot reloading. As of this article, there is a minor bug that prevents hot refreshing in the browser so you'll need to do a manual refresh when working in the React portion of the codebase. If you happen upon the fix for this, I would love to hear more about it! We're going to add a couple more dependencies that our webpack file will need. As well, since mix is managing our webpack, the file will be called `webpack.mix.js`.

```node
yarn add -D @babel/preset-react babel-loader @pmmmwh/react-refresh-webpack-plugin react-refresh;
```

*webpack.mix.js*
```javascript
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const webpack = require('webpack')
const mix = require('laravel-mix')

require('laravel-mix-tailwind')

const isDevelopment = process.env.NODE_ENV !== 'production'

mix
  .setPublicPath('public')
  .js('resources/client/index.js', 'public/js/')
  .react()
  .sass('resources/assets/scss/index.scss', 'public/css/')
  .tailwind()
  .options({
    processCssUrls: false
  })

if (isDevelopment) {
  mix.sourceMaps()
}

mix.webpackConfig({
  mode: isDevelopment ? 'development' : 'production',
  context: __dirname,
  node: {
    __filename: true,
    __dirname: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: require.resolve('babel-loader'),
            options: {
              presets: ['@babel/preset-react'],
              plugins: [isDevelopment && require.resolve('react-refresh/babel')].filter(Boolean),
            },
          },
        ],
      },
    ],
  },
  plugins: [
    isDevelopment && new webpack.HotModuleReplacementPlugin(),
    isDevelopment && new ReactRefreshWebpackPlugin(),
    new webpack.ProvidePlugin({
      React: 'react',
    }),
  ].filter(Boolean),
})
```

We'll also add additional fields to the .gitignore and let the deployment built handle them.

*.gitignore*
```
# other settings...

mix-manifest.json 
hot 
public/js/* 
public/css/*
public/**/*_js*
```

## Configure Tailwind

Let's go ahead and add tailwind configurations to our app.

```node
yarn add -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9 sass-loader@8.* sass postcss@^8.1;

mkdir -p resources/assets/scss && touch resources/assets/scss/index.scss;

npx tailwindcss init
```

*tailwind.config.js*
```javascript
module.exports = {
  purge: ['./resources/client/**/*.{js,jsx,ts,tsx}', './resources/views/**/*.edge'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}
```

*resources/assets/scss/index.scss*
```scss
@import "tailwindcss/base"; 
@import "tailwindcss/components"; 
@import "tailwindcss/utilities";
```

## Create Client React App

Next we'll need to bring in the needed React packages and create our starter React entry files.

```node
yarn add react react-dom;

mkdir -p resources/client && touch resources/client/index.js resources/client/App.js;
```

*resources/client/index.js*
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <App /> 
  </React.StrictMode>,
  document.getElementById('root')
);
```

*resources/client/App.js*
```javascript
import React from 'react'

export default function App() {
  return (
    <div>
      Hello World!
    </div>
  )
}
```

Since the server is handling the initial serve of our React app, we'll need to create an edge templating file that React will mount from. We'll do this in the resources/views folder. Our edge file for now will use direct references to our mix files instead of the mix templating syntax `{{ mix('scripts/index.js') }}`  due to deployment issues in Heroku (if that is what you decide to use).

```node
touch resources/views/index.edge;
```

*resources/views/index.edge*
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/css/index.css">
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script src="/js/index.js"></script>
  </body>
</html>
```

## Server Routing

Our server is hosting the React app so we now need let our routing know how to handle frontend routing. We'll "start" our route from the server and then from there, the React app will take over all client routing. You can manage client side routing using the popular routing library `react-router`.

*start/routes.ts*
```typescript
import Route from '@ioc:Adonis/Core/Route' 
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext' 

// Other Routes...

Route.get('*', async ({ view }: HttpContextContract) => { 
  return view.render('app') 
}).as('not_found')
```


## Development & Deployment

I've deployed my app to Heroku so you may find this linked article helpful in setting that up. As a result the package scripts referenced that reflect that flow but you may want or need to change them for deployment to suite your server environment.

```node
yarn add -D concurrently;
```

*package.json*
```
"start": "node build/server.js",
"server": "node ace serve --watch",
"client": "node ace mix:watch",
"build": "yarn client:build && yarn server:build",
"server:build": "node ace build --production",
"client:build": "node ace mix:build --production",
"dev": "concurrently \"yarn server\" \"yarn client\"",
```

*Procfile* (For Heroku deployment)
```
release: node ./build/ace migration:run --force && node ./build/ace db:seed
api: node build/server.js
web: node build/server.js
```

Minor notes:
1. `dev` runs both the server and the client for active development.
2. `start` and `build` are reserved for deployment in my use case

## Closing Thoughts

Currently, Adonis combined with React as a monorepo come with a couple minor inconveniences that should be solved either through library fixes or maybe there is something that I didn't notice. Hopefully these minor adjustments are resolved soon, but that that hasn't stopped me from continuing to use this setup in current projects.

Overall, running both your server and your client in the same environment bring some unique advantages that come with monorepos. You keep all the code together and CORS & APIs are easier to manage in regards to security & convenience. Also all stylesheets, tooling, and project wide changes are easy to distribute without having to adding in third party distributable libraries to multiple apps.

On the flip side, if you want or need separate projects for the server and client, this approach probably isn't for you. You'll most likely spin up a separate create-react-app (or custom) and deploy it elsewhere while calling the API endpoints of your Adonis server. There is nothing wrong with either approach. It all comes down to company and development objectives that best align with the future scalability your are looking for.

Here is a link to the [github](https://github.com/michaelmcshinsky/adonis-mix-react#readme) source code for reference.

----------

If you found this helpful or useful, please share a 💓, 🦄, or 🔖. Thanks!