API Controller/Routes Folder

Brandon Blankenstein
4 min readJun 4, 2022

--

A brief look into basic API folder structure with a focus on creating a“routes” and “controller” folder and why we might do that.

BASIC FOLDER STRUCTURE

A basic project may have a folder structure similar to this

API -
|
config -------------
| | |
| config.env db.js
|
controllers
| |
| file.js
|
models --
| |
| File.js
|
node_modules --
| |
|
routes --
| |
| file.js
|
package-lock.json
|
package.json
|
server.js

Inside of our API project we have:
- config: This folder should contain our .env file, which will have all of our environmental variables, and it should also have the db.js file that will connect to whatever database you choose to utilize.
- controllers: This folder will hold all the real meat of our API. This will eventually be where the “magic” happens.
- models: This folder will hold all of our Schemas. You’ll notice I capitalized the file in this folder above because this is a common naming convention for Schemas.
- routes: This folder will be used to help clean up our controller files by exporting our router methods and import them into our main server file (server.js).

SERVER.JS PRE ROUTES

API/server.jsconst express = require('express')
const dotenv = require('dotenv')
const PORT = process.env.PORT || 5000
const app = express()
app.get('/api/endpoint', (req, res) => {
// READ
}
app.post('/api/endpoint', (req, res) => {
// CREATE
}
app.put('/api/endpoint', (req, res) => {
// UPDATE
}
app.delete('/api/endpoint', (req, res) => {
// DELETE
}
app.listen(PORT, console.log(`Server running on PORT ${process.env.NODE_ENV}`))

We can keep all of these in a simple file because there is only one route per method. However, once we started wanting different variations and different endpoints then this file could easily become quite bloated.
This is where we add the routes folder.

ROUTES FOLDER

By creating a routes folder we can now separate the different CRUD methods from our server.js file. This allows each file to become less bloated as the project grows and to allow for a separation of concerns.
- require express so that we can access it’s Router() method
- replace the app. from earlier with our new router variable
- define router as our dependency that we’d like to export

API/routes/file.jsconst express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
// READ
}
router.post('/', (req, res) => {
// CREATE
}
router.put('/', (req, res) => {
// UPDATE
}
router.delete('/', (req, res) => {
// DELETE
}
module.exports = router

SERVER.JS POST ROUTES

We can now remove all of our CRUD methods and just focus on setting up our server. First, bring in the exported router. Last, mount the router onto the app. When we mount the router to the app we add the url endpoint, this is why we were able to remove the endpoint from our methods in the routes/file.

API/server.jsconst express = require('express')
const dotenv = require('dotenv')
// Bring in router
const router = require('./routes/file')
const PORT = process.env.PORT || 5000
const app = express()
// Mount router onto app
app.use('/api/endpoint', router)
app.listen(PORT, console.log(`Server running on PORT ${process.env.NODE_ENV}`))

CONTROLLERS FOLDER

The controllers folder will allow us to split the routes file into a further separation of concerns: Controllers will handle the endpoint methods, Routes will handle collecting and organizing all of our method calls.
This will look a lot like a javascript async function. We define the function as an export, create the parameters, and then create the inner workings of the function.
You’ll notice some comments, these allow for clarity when looking through the files and give a good description of what each method should do.

API/controllers/file.js//  @desc   Get 
// @route GET /api/endpoint
// @access PUBLIC
exports.getData = async (req, res, next) => {
// READ
}
// @desc POST
// @route POST /api/endpoint
// @access PRIVATE
exports.postData = async (req, res, next) => {
// CREATE
}
// @desc PUT
// @route PUT /api/endpoint
// @access PRIVATE
exports.putData = async (req, res, next) => {
// UPDATE
}
// @desc DELETE
// @route DELETE /api/endpoint
// @access PRIVATE
exports.deleteData = async (req, res, next) => {
// DELETE
}

UPDATED ROUTES FILE

Now that we have the controllers folder taking the meat of defining the api endpoint calls, we can utilize the files in the routes folder to further organize.
The first major change is that we must bring in the methods we created in the controllers/file. The next major change is that we call router.route. In this we define the specific url for these particular methods. These don’t have any special endpoint so we just use a single slash, but we could also use “router.route(‘/:id’) if we were utilizing an id in the url parameters.

API/routes/file.jsconst express = require('express')
const router = express.Router()
const {
getData,
postData,
putData,
deleteData
} = require('../controllers/file')
router.route('/')
.get(getData)
.post(postData)
.put(putData)
.delete(deleteData)
// extended url route example
router.route('/:id')
.get(getSingleDataByID)
module.exports = router

CONCLUSION

We started with a single server.js file that handled the server and all the endpoints. For a small project/example like this it may seem like overkill to create two new folders with nested files inside to separate a few lines of code.
However, when you look at utilizing this for a larger project it starts to make much more sense. We could have multiple files in the routes folder that are linked up with 1-to-1 number of files in the controllers folder.
Creating this separation of concerns starts to clean up our files and code as the project grows. Clean, readable code should be a goal for everyone because it allows you to quickly read your own code and for others to come in and learn how your code is set up. Organized code allows for quicker bug fixes and refactors.

Tip or Quote from Brandon:
Today I have a tip!

Take the leap. Do the thing you’ve been wanting to do that scares you. The thing that makes you both anxious and excited all at once.
Nothing good happens because you let it come to you. Somewhere along the way you made a choice that led you there.

--

--