How to use correctly NextJs API - node.js

i've just started using Next js with mongodb and i have a question about how should i organize the API route files.
I have a simple application that add, update and delete documents of a mongodb collection. For each operation i created a .ts file inside the api folder. Like this
And for example my new_task.ts file looks like this
export default async function AddTask (req:NextApiRequest, res:NextApiResponse) {
const task:Task = req.body
const client = await clientPromise;
const db = client.db("diary");
const myCollection: Collection = db.collection('tasks');
try {
await myCollection.insertOne(task)
res.send('Success')
} catch (error) {
res.status(400).json({error})
console.log(error)
}
}
Everything is working ok but i think it's kinda messy the file organization. Is there a way to put every operation inside just one file? Or to do so i would have to build a custom server with express?
Thanks

In one route function, you can check the request object req to see if the HTTP request method is GET POST PUT PATCH or DELETE. Depending on which method, you can call a different function.
Here is an example from the NextJS docs.
import type { NextApiRequest, NextApiResponse } from 'next'
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
Another thing you can do to make your code more re-useable and easier to maintain is to write reusable function definitions in a lib folder and then import them into your api route files when you want to use them.
Have you tried creating a file in the lib folder and writing function definitions there for MongoDB and then importing those function definitions into your api route file?
Then call the appropriate function depending upon the request method.
In ./lib/mongodb, write a function definition and import any Mongo-related imports you need.
export async function updateUserInfo(parameters) {
// . . . your code needs to return something, probably an array or object from MongoDB
}
In your api route file, import that function definition.
import { updateUserInfo } from "../../lib/mongodb"
Inside your route function, call updateUserInfo and pass whatever arguments you need based on the parameters you put in the definition. Handle its return value using await.
import type { NextApiRequest, NextApiResponse } from 'next'
import { updateUserInfo } from "../../lib/mongodb"
export default function userHandler(req: NextApiRequest, res: NextApiResponse) {
const {
query: { id, name },
method,
} = req
switch (method) {
case 'GET':
// Get data from your database
res.status(200).json({ id, name: `User ${id}` })
break
case 'PUT':
// Update or create data in your database
const updateResult = await updateUserInfo( . . .)
// FIX THE OBJECT IN .JSON BELOW TO SUIT YOUR CODE
res.status(200).json({ id, name: name || `User ${id}` })
break
default:
res.setHeader('Allow', ['GET', 'PUT'])
res.status(405).end(`Method ${method} Not Allowed`)
}
}
You can reuse updateUserInfo anywhere you have arguments for the required parameters.
Also, consider when you are calling the API route. At build time or after. At build, you call from static functions and after you call from client-side.
So by using the lib file for function definitions, you can reuse them in server functions and static functions.
The structure of the files inside the api folder is your api architecture. So it's organization depends upon your application's needs. You can use static and dynamic routes, as you maybe already know.
Consider API best practices when designing your architecture.

Related

Getting 500 Internal Server Error (using axios). Locally it gives error axiosError but it works fine when I hit refresh & everything loads [duplicate]

I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.

How can one upload an image to a KeystoneJS GraphQL endpoint?

I'm using TinyMCE in a custom field for the KeystoneJS AdminUI, which is a React app. I'd like to upload images from the React front to the KeystoneJS GraphQL back. I can upload the images using a REST endpoint I added to the Keystone server -- passing TinyMCE an images_upload_handler callback -- but I'd like to take advantage of Keystone's already-built GraphQL endpoint for an Image list/type I've created.
I first tried to use the approach detailed in this article, using axios to upload the image
const getGQL = (theFile) => {
const query = gql`
mutation upload($file: Upload!) {
createImage(file: $file) {
id
file {
path
filename
}
}
}
`;
// The operation contains the mutation itself as "query"
// and the variables that are associated with the arguments
// The file variable is null because we can only pass text
// in operation variables
const operation = {
query,
variables: {
file: null
}
};
// This map is used to associate the file saved in the body
// of the request under "0" with the operation variable "variables.file"
const map = {
'0': ['variables.file']
};
// This is the body of the request
// the FormData constructor builds a multipart/form-data request body
// Here we add the operation, map, and file to upload
const body = new FormData();
body.append('operations', JSON.stringify(operation));
body.append('map', JSON.stringify(map));
body.append('0', theFile);
// Create the options of our POST request
const opts = {
method: 'post',
url: 'http://localhost:4545/admin/api',
body
};
// #ts-ignore
return axios(opts);
};
but I'm not sure what to pass as theFile -- TinyMCE's images_upload_handler, from which I need to call the image upload, accepts a blobInfo object which contains functions to give me
The file name doesn't work, neither does the blob -- both give me server errors 500 -- the error message isn't more specific.
I would prefer to use a GraphQL client to upload the image -- another SO article suggests using apollo-upload-client. However, I'm operating within the KeystoneJS environment, and Apollo-upload-client says
Apollo Client can only have 1 “terminating” Apollo Link that sends the
GraphQL requests; if one such as apollo-link-http is already setup,
remove it.
I believe Keystone has already set up Apollo-link-http (it comes up multiple times on search), so I don't think I can use Apollo-upload-client.
The UploadLink is just a drop-in replacement for HttpLink. There's no reason you shouldn't be able to use it. There's a demo KeystoneJS app here that shows the Apollo Client configuration, including using createUploadLink.
Actual usage of the mutation with the Upload scalar is shown here.
Looking at the source code, you should be able to use a custom image handler and call blob on the provided blobInfo object. Something like this:
tinymce.init({
images_upload_handler: async function (blobInfo, success, failure) {
const image = blobInfo.blob()
try {
await apolloClient.mutate(
gql` mutation($image: Upload!) { ... } `,
{
variables: { image }
}
)
success()
} catch (e) {
failure(e)
}
}
})
I used to have the same problem and solved it with Apollo upload link. Now when the app got into the production phase I realized that Apollo client took 1/3rd of the gzipped built files and I created minimal graphql client just for keystone use with automatic image upload. The package is available in npm: https://www.npmjs.com/package/#sylchi/keystone-graphql-client
Usage example that will upload github logo to user profile if there is an user with avatar field set as file:
import { mutate } from '#sylchi/keystone-graphql-client'
const getFile = () => fetch('https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
{
mode: "cors",
cache: "no-cache"
})
.then(response => response.blob())
.then(blob => {
return new File([blob], "file.png", { type: "image/png" })
});
getFile().then(file => {
const options = {
mutation: `
mutation($id: ID!, $data: UserUpdateInput!){
updateUser(id: $id, data: $data){
id
}
}
`,
variables: {
id: "5f5a7f712a64d9db72b30602", //replace with user id
data: {
avatar: file
}
}
}
mutate(options).then(result => console.log(result));
});
The whole package is just 50loc with 1 dependency :)
The easies way for me was to use graphql-request. The advantage is that you don't need to set manually any header prop and it uses the variables you need from the images_upload_handler as de docs describe.
I did it this way:
const { request, gql} = require('graphql-request')
const query = gql`
mutation IMAGE ($file: Upload!) {
createImage (data:
file: $file,
}) {
id
file {
publicUrl
}
}
}
`
images_upload_handler = (blobInfo, success) => {
// ^ ^ varibles you get from tinymce
const variables = {
file: blobInfo.blob()
}
request(GRAPHQL_API_URL, query, variables)
.then( data => {
console.log(data)
success(data.createImage.fileRemote.publicUrl)
})
}
For Keystone 5 editorConfig would stripe out functions, so I clone the field and set the function in the views/Field.js file.
Good luck ( ^_^)/*

Setting default state for Redux store via axios

I have a node server that performs CRUD operations on data stored in a MongoDB Atlas database. My front-end is made using react where i am using redux for state management. Before I had the backend setup, I was initializing the default state of the redux store by calling a function i made which just returns JSON data. Now I want to make a get request to the server via axios to retrieve that same JSON data which is now on the Mongo database.
I know that I should be making axios get calls in componentDidMount lifecycle hook but my store.js is not a class but i'm not sure how. I am however able to just do axios.get(URL) but it returns an object in the form of [[PromiseStatus]]:"resolved", [[PromiseValue]]:{the data i want}. I read that these are not accessible. Im wondering if it is because I am not making the axios call in the right place or in the right time in the lifecycles.
import { createStore } from "redux";
//import { syncHistoryWithStore } from "react-router-redux";
//import { browserHistory } from "react-router-dom";
import axios from "axios";
//import the root reducer.
import rootReducer from "../reducers/rootReducer";
//this is just getting JSON from a file
import { getAnnotations } from "../services/fakeEntry";
//create an object for default data
const defaultState = {
//This is how i was creating the default state before.
// metadata: getAnnotations()
//this is what id like to do.But this needs to be in a lifecycle hook?
metadata: axios
.get("http://localhost:5100/data/")
.then(response => {
return response;
})
.catch(function(error) {
console.log(error);
})
};
const store = createStore(rootReducer, defaultState);
export default store;
The code above is the redux store. I have tested the endpoint using postman and it returns the JSON data. When i read this redux state using mapStateToProps in another component i get returned a Promise object.
I see two mistakes in your approach.
Redux is built from principles of functional programming and based on pure functions (without side effects, like API calls). So default state should some default empty object like below
const defaultState = {
metadata: {} // It should be empty during store init
isDataInitialized: false // You can add additional property to denote, that data is not fetched for the first time
}
axios returns Promise (as you already discovered). So take data from Promise, you should either user .then and set data in callback, or use async\await
Here is sample using async\await
// Now it is function, not object
const getInitalData = () => async dispatch => {
try {
let metadata = await axios
.get("http://localhost:5100/data/");
// You're dispatching not only the metadata, but also setting isDataInitialized to true, to denote, that data has been loaded
dispatch ({ type: 'DATA_INITIALIZED', metadata, isDataInitialized: true });
}
catch {
console.log(error);
}
}
Essentially getInitalData is action creator, which will dispatch action DATA_LOADED to store.
And you should create store with middleware (like thunk), to be able to dispatch actions which are functions.
const store = createStore(rootReducer, applyMiddleware(
thunkMiddleware
))
defaultState should go to reducer directly, like below
rootReducer (state = defaultState, action) {
// ...
Then, in some root component of your app, you call getInitalData to load data from server to store.
Here is simple sample you can use for better understanding

Sharing DB queries in Node.js methods

What's the best practice for sharing DB query code between multiple Node.js Express controller methods? I’ve searched but the samples I’ve found don’t really get into this.
For example, I have this getUser method (using Knex for MySQL) that makes a call to get user info. I want to use it in other methods but I don't need all the surrounding stuff like the response object.
export let getUser = (req: Request, res: Response, next: NextFunction) => {
try {
knex.select().where('email', req.params.email)
.table('users')
.then( (dbResults) => {
const results: IUser = dbResults[0];
res
.status(200)
.set({ 'Content-Type': 'application/json', 'Connection': 'close' })
.send(results);
});
} catch (err) {
res.send({ error: "Error getting person" + req.params.email });
return next(err);
}
};
It seems wrong to repeat the query code somewhere else where I need to get the user. Should I turn my DB query code into async functions like this example and then call them from within the controller methods that use the query? Is there a simpler way?
/**
* #param {string} email
*/
async function getUserId(email: string) {
try {
return await knex.select('id')
.where('email', email)
.table('users');
} catch (err) {
return err;
}
}
You can for example create "service" modules, which contain helpers for certain type of queries. Or you could use ORM and implement special queries in each model that is called "fat model" design. Pretty much anything goes as long as you remember to not create new knex instance in every helper module, but you just pass knex (containing its connection pool) for the helper methods so that all queries will share the same connection pool.
ORM's like objection.js also provides a way to extend query builder API so you can inherit custom query builder with any special query helper that you need.

How would I create a new document, populate it, and then send it back as json?

I would like to update my API call that creates a new document, to create the new document, populate it, and then return the json response via express.
I have my model set up as follows:
import mongoose from 'mongoose';
import constants from '../../constants/enums';
const DepartmentBudgetSchema = new mongoose.Schema({
department : {
type : String,
enum : constants.departments
},
owner : {
type : mongoose.Schema.Types.ObjectId,
ref : 'Executive'
},
budget : Number
});
export default mongoose.model('DepartmentBudget', DepartmentBudgetSchema);
I have my API calls setup with my express paths, which make a call to my logic, which in turn makes a call to my controller, where I handle all the database work.
My express api call looks like the following:
import express from 'express';
import DepartmentsBudgetsLogic from './departmentBudget.logic.js';
import constants from '../../constants/enums';
const router = express.Router();
const code = constants.statusCode;
router.post('/', newDepartmentBudget);
async function newDepartmentBudget( req, res ) {
try {
const result = await new DepartmentsBudgetsLogic().newDepartmentBudget(req.body);
console.log(result);
res.status(code.created).json(result);
}
catch ( error ) {
console.error('Unable to create department budget.', error);
res.sendStatus(code.serverError);
}
}
Then my logic function simply calls my controller passing the budget object:
import DepartmentBudgetController from './departmentBudget.controller';
export default class DepartmentBudgetLogic {
newDepartmentBudget( budget ) {
return new DepartmentBudgetController().newDepartmentBudget(budget);
}
}
Finally, and this is where I think I am having my problem, the controller handles the mongoose work:
import DepartmentBudget from './departmentBudget.model';
export default class DepartmentBudgetController {
newDepartmentBudget( budget ) {
DepartmentBudget.create(budget).then(function( budget ) {
return budget.populate('owner').execPopulate();
});
}
}
The document gets created in the database correctly, but I am really struggling with the code to populate the owner field and then return the populated doc as my response.
I know from mongoose's docs that execPopulate() returns a promise that resolves to the populated document, but when I console.log the result in my express api call I get "undefined".
I'm still learning here, so I may have overlooked something very obvious.
Anyone have any ideas on what I'm doing wrong? Or is there a different direction I should go in so that I can accomplish the overall objective of being able to create a new document, populate it, and then return it in my response object.
Thank you!

Resources