Fullstackopen Summary Part 3
Tags: react, tutorial, webdev, javascript
Published: 2023-11-15 15:00:55

This post contains the summary of what I learnt in fullstackopen (Part 3) 

**Preparing the backend**

Create a directory for your backend and use
```
npm init
```
to create a `package.json` file.
Create a file called `index.js` to implement your backend code and to run it, you need to use
```
node index.js
```

**Implement the server code**

In order to handle server side development,
we need to use `express`
To install it, run:
```
npm install express
```
Next, in index.js file, import the express library and use it.

```
const express = require('express')
const app = express()
```

**Listening on a port**

```
const PORT=3001
app.listen(PORT, ()=> {
  console.log(`Server running on port ${PORT}`)
})
```

**GET Request to show on local server**

```
let data = [ ... data in json format ... ]

app.get('/', (request, response) => {
  response.send('<h1>Hello World!</h1>')
})

app.get('/api/data', (request, response) => {
  response.json(data)
})
```

**Observe live changes in the server**

In order to observe live changes in the server, we need to use nodemon

```
npm install nodemon --save-dev
```
To observe live changes in the server use:
```
nodemon index.js
```

**Defining parameters for routes**

```
app.get('/api/notes/:id', (request, response) => {
  const id = request.params.id
  const note = notes.find(note => note.id === id)
  response.json(note)
})
```
`request.params.id` contains the parameter id from the route url.

**Emulate requests to the backend**
Use POSTMAN to simulate requests to the backend.

**Posting data to the backend**

```
app.use(express.json())
```
This allows sending data to the backend in json format

```
app.post('/api/notes', (request, response) =>{
  const note = request.body  
  response.json(note)
})
```

**Middlewares**

Middlewares are functions used for handling request and response objects.
Used by the express server like this:
```
app.use(middleware)
```

**CORS**
In order to allow the client to access data from a server hosted on a different website or server, express needs to enable cors.

In terminal:
```
npm install cors
```
In index.js
```
const cors = require('cors')
app.use(cors())
```

**Production Build**

To deploy the app, we must first create the production build which is made by
```
npm run build
```
which creates a folder called `dist` containing the build files.

Once the production build has been run, you can move it to the backend folder and then allow the backend to use the static content using:

```
app.use(express.static('dist'))
```

**Proxy**
We can use proxy to shorten the base url link since the backend is hosted on another url.
To do so, go to `vite.config.js` file and add the following:

```
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {    
   proxy: {      
    '/api': {        
      target: 'http://localhost:3001',
      changeOrigin: true,      
    },    
  }  
 },
})
```

**Saving data to mongodb**

Go to mongodb atlas to create a cluster and get a mongodb uri like:
`mongodb+srv://fullstack:<password>@cluster0.o1opl.mongodb.net/?retryWrites=true&w=majority`

To use the mongodb database, we need to use a node package called mongoose which can be installed using 

```
npm install mongoose
```
and to implement it, you need to go to the server side and add the following:

```
const mongoose = require('mongoose')
```

To use the specific mongodb cluster, we need to use the cluster's uri which should be saved as an environment variable in a `.env` file.

To set it up, you need to install `dotenv`
and import it using:
```
require('dotenv').config()
```

let's say the variable name is `MONGO_URI`, so to use it, you need to access it by `process.env.MONGO_URI`.

**Configuring the schema**

```
mongoose.set('strictQuery',false)
```
ensures that only the items stated in the schema gets saved into the mongodb database.

Then we connect the mongodb database to our backend using:
```
mongoose.connect(process.env.MONGO_URI)
```

Next, we declare a schema to set the basis of the data that our database will hold using:

```
const schema = new mongoose.Schema({
  item_1: datatype,
  item_2: datatype,
  ....
})
const Object = mongoose.model('Object',schema)

<create an entry using that schema, this part is demonstrated later on>

entry.save().then(res => {
  mongoose.connection.close()
})
```

**Fetching items from the database**
We previously made an instance called `Object` which followed the schema we made.

In order to get all the entries in that object, we use:

```
app.get('/api/data'.(req,res) => {
  Object.find({}).then(result=>{
    res.json(result)
  })
})
```

**Transforming mongodb output**

The response from the mongodb database comes with some default values such as `_id` and `__v`.
These needs to be transformed into attributes that are _cleaner_.

Implement that by using:
```
schema.set('toJSON', {
  transform: (document, returnedObject) => {
    returnedObject.id = returnedObject._id.toString()
    delete returnedObject._id
    delete returnedObject.__v
  }
})
```

Once you save this configuration, you need to export it by:
```
module.exports = mongoose.model('Object',schema)
```

**Error Handling**
We can handle the errors in a middleware, in the middleware's HTTP request, you can do the following:

```
app.get('/api/notes/:id', (request, response, next) => {  Note.findById(request.params.id)
    .then(note => {
        ...
    .catch(error => next(error))})
```

the `next` function sends the error down the next routes till it reaches the errorhandler middleware.

You can initiate the errorhandler middleware like:

```
const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } 

  next(error)
}

// this has to be the last loaded middleware.
app.use(errorHandler)
```

**The order of middleware loading**

```
app.use(express.static('dist'))
app.use(express.json())
app.use(requestLogger)

app.post('/api/notes', (request, response) => {
  const body = request.body
  // ...
})

const unknownEndpoint = (request, response) => {
  response.status(404).send({ error: 'unknown endpoint' })
}

// handler of requests with unknown endpoint
app.use(unknownEndpoint)

const errorHandler = (error, request, response, next) => {
  // ...
}

// handler of requests with result to errors
app.use(errorHandler)
```

**Delete and Update Requests**

Delete
```
app.delete('/api/notes/:id', (request, response, next) => {
  Note.findByIdAndRemove(request.params.id)
    .then(result => {
      response.status(204).end()
    })
    .catch(error => next(error))
})
```

Update
```
app.put('/api/notes/:id', (request, response, next) => {
  const body = request.body

  const note = {
    content: body.content,
    important: body.important,
  }

  Note.findByIdAndUpdate(request.params.id, note, { new: true })
    .then(updatedNote => {
      response.json(updatedNote)
    })
    .catch(error => next(error))
})
```

**Enforce Validation**

```
const noteSchema = new mongoose.Schema({
  content: {    
    type: String,    
    minLength: 5,    
    required: true  
  },  
  important: Boolean
})
```

Add this to errorhandler

```
else if (error.name === 'ValidationError') {    
  return response.status(400).json({ error: error.message })  }
```

Add this to the update request:
```
 Note.findByIdAndUpdate(
    request.params.id, 
    { content, important },    
    { new: true, runValidators: true, context: 'query' }  
) 
```