Setting up a full-stack application with Node, Express, Vite, React, tailwindcss, twin.macro and styled-components
For small to medium size applications
Table of contents
- The Stack 🚀
- 🤔 Why vite over create-react-app?
- 🤔 Why twin.macro, tailwindcss, and styled-components for styling?
- 🤔 Why node.js and express?
- ⚙️ The Setup
- Step 1️⃣ : Create a project, Initialise/setup the server using npm and segregate the backend (server) and frontend (client) responsibilities
- Step 2️⃣ : Initialize the React application inside client folder using vite and configure vite.config.js to proxy any API requests to the server
- Step 3️⃣ : Write server code and start the development environment
- Step 4️⃣: Create a simple Application page in react using tailwindcss and refactor with twin.macro
- 🔗 GitHub repository
- 💭 Conclusion
The Stack 🚀
vite: a fast, lightweight frontend build tool that uses ES modules and service workers to provide a smooth development experience.
tailwindcss: a utility-first CSS framework for rapidly building custom user interfaces.
twin.macro: a Babel macro that enables users to write CSS in JavaScript using a Tagged Template Literal syntax similar to styled-components.
styled-components: a library for creating and styling reusable React components using a familiar CSS-in-JS syntax.
node.js: an open-source, cross-platform JavaScript runtime environment that executes JavaScript code outside of a web browser.
express: a minimalist web framework for Node.js that provides a set of features for building web applications and APIs.
🤔 Why vite
over create-react-app
?
Vite is built around the concept of a modern JavaScript bundler called
esbuild
, which is designed to be fast and lightweight. This can result in faster builds and rebuilds when compared tocreate-react-app
, which useswebpack
.Vite includes a built-in development server that utilizes native ES module imports and service workers to provide a smooth development experience with hot module reloading (HMR).
Vite has a simple configuration file
(vite.config.js)
that is easy to understand and customize.Vite is designed to be flexible and extensible, and it provides a plugin system that allows users to customize the build process or add additional features.
Vite has a smaller footprint than
create-react-app
, as it does not include a lot of the boilerplate and configuration that comes withcreate-react-app
by default. This can be a benefit for developers who want a simpler, more lightweight tool.
🤔 Why twin.macro
, tailwindcss
, and styled-components
for styling?
twin.macro
allows you to use the syntax and features ofstyled-components
to define your styles, while still taking advantage of the utility-first approach oftailwindcss
.The combination of
twin.macro
andtailwindcss
can make it easier to build custom user interfaces quickly, as you can use the predefined classes provided bytailwindcss
to style your components, and usetwin.macro
to override or extend those styles as needed.Using
styled-components
withtwin.macro
andtailwindcss
can help to keep your component-level styles organized and maintainable, as you can define all of the styles for a component in a single place.twin.macro
can help to improve the performance of your application by automatically purging unused styles from your CSS, reducing the size of your stylesheets and improving the loading time of your application.The combination of
twin.macro
,tailwindcss
, andstyled-components
can provide a powerful and flexible toolset for styling your React applications, enabling you to build custom user interfaces with ease.
🤔 Why node.js
and express
?
Node.js
is built on top of the V8JavaScript
runtime, which makes it efficient and fast for building scalable network applications.Node.js
has a large and active developer community, which means there are many resources available for learning and troubleshooting.Express
is a minimalist web framework forNode.js
that provides a set of features for building web applications and APIs. It is easy to learn and use, and it has a small footprint, making it well-suited for intermediate-level full-stack applications.Express
is built on top of themiddleware
concept, which makes it easy to extend and customize the functionality of your application.Node.js
andExpress
are well-suited for building real-time applications, such as chat applications or collaborative tools, as they can handle a high number of concurrent connections and provide low latency.Both
Node.js
andExpress
have a large ecosystem of third-party libraries and plugins that can be easily integrated into your application, allowing you to build a wide range of functionality with minimal effort.
Now that we have set the stack and reasons for using the specifics out of the way let's dive right into the setup process of our application.
I will be using:
Visual Studio Code (vscode) for this demonstration as it is my favourite of all other code editors and I am very comfortable using it. You can any code editor of your choice.
Node Package Manager (NPM) as my default package manager, You can also use
yarn
.
⚙️ The Setup
Step 1️⃣ : Create a project, Initialise/setup the server using npm
and segregate the backend (server) and frontend (client) responsibilities
create a new folder and open it inside vscode
open the terminal in vscode using
command + j
on mac andctrl + j
on windowsrun
npm init -y
in the terminal
npm init -y
is a command that is used to create a newpackage.json
file for a Node.js project. The-y
flag tells npm to use the default values for the package.json fields, so you don't have to answer any prompts.
you will a new file
package.json
created in the root of your project looking something like this{ "name": "fullstackdemo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
once the
package.json
has been initialised, we need to create a new.js
file in the root that will contain our main server logic. I will be naming itindex.js
next, create a new folder in the root of your project (next to the above created
package.json
) I am naming itclient.
This will contain all our code related to frontend that we will be creating usingvite,
shortly.now, we need to install the necessary dependencies on our server-side. in your terminal, run:
npm install concurrently express nodemon
This will create a
node_modules
folder where all the dependencies for the server will be saved. We can also notice that thepackage.json
contents have now been updated.while we are at our server's
package.json
let's also update the scripts that will run our server and clientconcurrently
. We are usingnodemon
to automatically rerun our server whenever we make any changes to our code."scripts": { "server": "nodemon index.js", "client": "npm run dev --prefix client", "dev": "concurrently \"npm run server\" \"npm run client\"" },
and this is how the
package.json
should be looking at this point:{ "name": "fullstackdemo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "server": "nodemon index.js", "client": "npm run dev --prefix client", "dev": "concurrently \"npm run server\" \"npm run client\"" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "concurrently": "^7.6.0", "express": "^4.18.2", "nodemon": "^2.0.20" } }
Step 2️⃣ : Initialize the React application inside client
folder using vite
and configure vite.config.js
to proxy any API requests to the server
Inside your
client
folder run:npm create vite@latest
and complete the prompt by giving the inputs asked. Enter
.
for your project name so that the project is initialised in theclient
folder itself. Choosereact
when asked to select a framework andjavascript
as the variant for this demo.The folder structure should be looking like this on successful initialisation.
next, install all the dependencies that are needed for setting up our react client.
npm install @emotion/core @emotion/react @emotion/styled styled-components twin.macro vite-plugin-babel-macros
once we have these dependencies installed, we can proceed to set up
tailwindcss
in our react applicationFollow the official tailwind guide to setup with vite here: https://tailwindcss.com/docs/guides/vite
Note: since we have already setup our client folder, we can follow the documentation step 2 onwards.
after completing the
tailwindcss
setup you will have two new files in your client folder,tailwind.config.cjs
andpostcss.config.cjs
next, we need to configure vite. This will be done by adding rules to
vite.config.js
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import macrosPlugin from 'vite-plugin-babel-macros'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [macrosPlugin(), react()], server: { port: 3000, proxy: { '/api': { target: 'http://localhost:6001', changeOrigin: true, secure: false, ws: true, }, }, }, });
❗️plugins:
vite-plugin-babel-macros
is a Vite plugin that allows you to use advanced code transformations (macros) at compile time, in React projects, without a complex build system. It usesbabel-plugin-macros
and allows you to import macros from libraries likebabel-plugin-react-css-modules
to transform CSS modules into CSS classes at compile time, streamlining the development process for React projects.❗️server:
The
port
property is set to3000
, which means that the development server will run on port 3000. This means that when you start the development server, you'll be able to access your application in your web browser athttp://localhost:3000
.The
proxy
property is set to an object that defines a single proxy route. The proxy is used to forward certain requests from the development server to another server or service.The route
'/api'
is defined, this is a path that when the development server receives a request for it, will forward the request to the target specified.target
property is set tohttp://localhost:6001
, This is the URL of the target server. All requests to/api
will be forwarded to this URL.changeOrigin: true
change the request's origin from localhost to the target origin.secure: false
this means that self-signed SSL certificates will be accepted.ws: true
WebSocket proxy, If you're proxying WebSocket connections, this property should be set totrue
.
This setup allows you to run the frontend and the backend on different ports and different servers but the frontend can make the requests to the backend as if they were on the same origin.
This means that requests to
http://localhost:3000/api/*
will be forwarded tohttp://localhost:6001/*
allowing your front-end to interact with your back-end services as if they were running on the same server.It's worth noting that the
secure: false
andws: true
are optional, You can leave it out or set it tofalse
if you don't want it.
Step 3️⃣ : Write server code and start the development environment
We will be creating a simple API endpoint to fullfill a simple GET request that returns some static data sufficient for this demonstration.
inside
index.js
of your root directory, add the following code:const express = require('express'); const app = express(); const ENV = 'development'; const DOMAIN = ENV === 'development' ? 'localhost' : ''; const PORT = 6001; app.use(express.json({ extended: false })); app.get('/api', (req, res) => { try { const mockData = { firstName: 'Adeesh', lastName: 'Sharma' }; res.json(mockData); } catch (err) { res.status(500).json({error: err.message }); } }); app.listen(PORT, `${DOMAIN}`, () => { console.log(`Server listening on port ${PORT}`); });
now, fire up the dev server by running the following command in the root folder
npm run dev
test the
react
application by navigating tolocalhost:3000
in the browser
- test the API endpoint by hitting
localhost:3000/api
🎉 We have successfully set up a full-stack application running express in the backend and serving a react client 🎉
Step 4️⃣: Create a simple Application page in react using tailwindcss
and refactor with twin.macro
contents of
client/src/App.js
import React, { useState, useEffect } from 'react'; import './index.css'; const App = () => { const [data, setData] = useState(null); useEffect(() => { const apiCall = async () => { const response = await fetch('/api'); const data = await response.json(); setData(data); }; apiCall(); }, []); return ( <div className='bg-indigo-200 h-screen'> <div className='max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8'> <div className='text-center'> <h2 className='text-base font-semibold text-indigo-600 tracking-wide uppercase'> Demonstration </h2> <p className='text-red-600 mt-1 text-4xl font-extrabold sm:text-5xl sm:tracking-tight lg:text-6xl'> Full-stack application </p> {data && ( <p className='max-w-xl mt-5 mx-auto text-xl text-gray-500'> with ♥️ by {data.firstName} {data.lastName} </p> )} </div> </div> </div> ); }; export default App;
refactor with
twin.macro
andstyled-components
import React, { useState, useEffect } from 'react'; import './index.css'; import styled from 'styled-components'; import tw from 'twin.macro'; import vite from '/vite.svg'; const App = () => { const [data, setData] = useState(null); useEffect(() => { const apiCall = async () => { const response = await fetch('/api'); const data = await response.json(); setData(data); }; apiCall(); }, []); return ( <ApplicationContainer> <SectionContainer> <Image src={vite} /> <CenteredText> <H2>Demonstration</H2> <Title>Full-stack application</Title> {data && ( <Author> with ♥️ by {data.firstName} {data.lastName} </Author> )} </CenteredText> </SectionContainer> </ApplicationContainer> ); }; export default App; const ApplicationContainer = tw.div`bg-indigo-200 h-screen`; const SectionContainer = tw.div`max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8`; const CenteredText = styled.div` ${tw`text-center`} `; const H2 = styled.h2` font-weight: 800; ${tw`text-base text-indigo-600 tracking-wide uppercase`} `; const Title = tw.p`text-red-600 mt-1 text-4xl font-extrabold sm:text-5xl sm:tracking-tight lg:text-6xl`; const Author = tw.p`max-w-xl mt-5 mx-auto text-xl text-gray-500`; const Image = styled.img` ${tw`h-40 flex justify-center w-full`} `;
Final Product 👏🏻
🔗 GitHub repository
Link: https://github.com/adeeshsharma/fullstack-setup
💭 Conclusion
A development setup that uses Node, Express, Vite, React, Tailwind CSS, twin.macro, and styled-components is an excellent choice for building modern web applications. Each of these technologies offers powerful and flexible features that can help to reduce development time and streamline the process of building web applications.
Node and Express provide a robust and scalable foundation for building back-end services, while Vite offers a fast and easy-to-use development server that allows you to quickly test and iterate on your front-end code. React, on the other hand, is a popular and widely used JavaScript library that makes it easy to build reusable UI components, while Tailwind CSS is a utility-first CSS framework that can help to speed up the process of styling your application.
twin.macro and styled-components can further reduce development time by allowing you to use advanced code transformations at compile time, without having to use a complex build system. twin.macro which is a library that allows you to use macros in your React code and styled-components is a library that allows you to write CSS styles in JavaScript, and it can be used to style React components.
In conclusion, this setup is a powerful and efficient way to build web applications, thanks to the simplicity and flexibility provided by Node, Express, Vite, React, Tailwind CSS, twin.macro and styled-components. It can save you a lot of development time, compared to other frameworks and libraries, by simplifying the process of building modern web applications.