Both of these blocks of code redirect to products/:id. However, the path I have to introduce is different.
in the post route I have to include products/, but in the put route I don't.
Does anybody know why this is the case? Thank you for your time.
router.post("/", async (req, res) => {
const newProduct = new Product(req.body)
await newProduct.save()
console.log(newProduct)
res.redirect(`products/${newProduct.id}`)
})
router.put("/:id", async (req, res) => {
const { id } = req.params
const product = await Product.findByIdAndUpdate(id, req.body,
{
runValidators: true,
new: true
})
console.log(req.body)
res.redirect(`${product._id}`)
}
Redirections happen relative to the URL of the request that triggered them.
The POST route is triggered by POST /products.
The PUT route is triggered by PUT /products/123.
Relative evaluation effectively replaces the last segment of the triggering URL with the relative URL. This can be demonstrated with the URL class in Node.js:
> new URL("newid","http://server/products").href
'http://server/newid'
> new URL("products/newid","http://server/products").href
'http://server/products/newid'
> new URL("id","http://server/products/id").href
'http://server/products/id'
Related
Devs at my startup have switched login to a magic link system, in which you get inside after clicking a link on the email body.
I have set up a Mailsac email to receive mails containing magic links but I haven't been able to actually follow those links because of the following:
cy.request({
method: "GET",
url: "https://mailsac.com/api/addresses/xxxx#mailsac.com/messages",
headers: {
"Mailsac-Key": "here-goes-the-key",
},
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.origin(magicLink, () => {
cy.visit('/')
});
});
I wasn't able to use cy.visit() either because the magic link URL is slightly different from the baseURL in this testing environment.
So my question is:
How could I follow this cumbersome link to find myself logged in home, or else, is there another way to deal with magic links?
Thanks
The docs say
A URL specifying the secondary origin in which the callback is to be executed. This should at the very least contain a hostname, and may also include the protocol, port number & path. Query params are not supported.
Not sure if this means the cy.visit() argument should not have query params, of just the cy.origin() parameter.
Try passing in the link
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
const magicOrigin = new URL(magicLink).origin
cy.origin(magicOrigin, { args: { magicLink } }, ({ magicLink }) => {
cy.visit(magicLink)
});
});
If that doesn't fix it, you could try using cy.request() but you'll have to observe where the token is stored after using the magicLink.
cy.request({
...
}).then((res) => {
const magicLink = res.body[0].links[0];
cy.request(magicLink).then(response =>
const token = response??? // find out where the auth token ends up
cy.setCookie(name, value) // for example
});
});
You need to pass the domain as the first parameter to origin, and do the visit within the callback function, something like this:
const magicLinkDomain = new Url(magicLink).hostname
cy.origin(magicLinkDomain, {args: magicLink}, ({ magicLink }) => {
cy.visit(magicLink);
//...
})
Reference: https://docs.cypress.io/api/commands/origin#Usage
I found one way. I can store base url in AsyncStorage so that the user can reload the page and still have access to that url. But there is one problem. I can’t have asynchronous code inside RTK endpoints.
const postAuthEndpoint = api.injectEndpoints({
endpoints: build => ({
postAuth: build.mutation<PostAuthResponse, PostAuthRequest>({
query: async data => {
// throws an error:
// Type 'Promise<{ url: string; method: string; body: PostAuthRequest; }>'
// is not assignable to type 'string | FetchArgs'
const baseUrl = await AsyncStorage.getItem('baseUrl');
return {
url: `${baseUrl}/auth`,
method: 'POST',
body: data,
};
},
}),
}),
});
Because of this, I decided to create a custom hook, that performs an async operation to get the base url. Then the custom hook passes the base url to the api hook from RTK-Query, which we pass to the custom hook. And returns wrapped mutation with the rest of parameters.
export const useEndpointWrapper = (endpoint: any) => {
const [mutation, ...rest] = endpoint;
const wrappedMutation = async (args: Request) => {
const baseUrl = await AsyncStorage.getItem('baseUrl');
return mutation({ baseUrl, ...args }).unwrap();
};
return [wrappedMutation, rest];
};
The main disadvantage here is that the TypeScript typing breaks down. This is solvable, but inconvenient.
Maybe there are some other ways to substitute the dynamic base url in react native?
I have separated my routes with their definitions so that my routes look like this:
const router = require('express').Router();
const handle = require('../handlers');
router.post('/register', handle.register);
// The handler defines the route. Like this:
exports.register = async (req, res, next) => {
try {
const user = await db.User.create(req.body);
const {id, username} = user;
res.status(201).json({id, username});
} catch (err) {
if(err.code === 11000){
err.message ='Sorry, details already taken';
}
next(err);
}
};
This works fine. Now I need to upload images as users register (using multer middleware). A lot of the examples show multer is used like this:
router.post('/register', upload ,function (req, res, next) { //upload is the multer middleware
console.log(req.file);
})
How do I add the middleware in my case?
You add the middleware like this:
router.post('/register', upload, handle.register);
Just like the examples you see.
In My Not-So-Humble Opinion
You mention that you have separated your routes from your route handler. In my opinion this is not only misguided but completely wrong.
In the usual MVC architecture you see in other languages, the route and the route handler together makes the controller. In your design you have split your controller into two separate structures. Primarily, this makes your route handling logic and the information of what route it does it for exist in two separate files when they should be located very close to each other.
Now that you are adding middlewares you are finding out that middleawares are installed in routes and with your design it is hard to figure out what middleware is installed for your route handler.
The correct way to separate your business logic from your routes is to follow the MVC design pattern - separate your Model from your Controller (the View is free because it is basically res.json()).
In my not-so-humble opinion you should be creating a model for your user instead of separating controllers into two parts. Your user obviously need a register functionality and the register function should just succeed or fail but should not concern itself with any HTTP error handling because that is the controller's job:
exports.user = {
// .. other methods
register: async function (username, password) {
try {
const user = await db.User.create({username, password});
return user;
} catch (err) {
if(err.code === 11000){
err.message ='Sorry, details already taken';
}
throw err;
}
}
}
Then in your controller (most Express example call "route") you do:
const user = require('./user');
router.post('/register', upload , async (req, res, next) => {
try {
const user = user.register(req.body.username, req.body.password);
const {id, username} = user;
res.status(201).json({id, username});
} catch (err) {
next(err);
}
})
However, this is just my personal opinion.
I can't share var between routes in my index.js. I need to use var page from first route in the second route. Is necesary a closure for this? I have this two routes:
First route:
router.get('/:page', async (req, res, next) =>{
var perPage = 5;
var page = req.params.page || 1;
console.log(page);
const expedientes = await Expediente
.find()
.skip((perPage*page)-perPage)
.limit(perPage)
.exec((err, expedientes)=> {
Expediente.count().exec((err, count)=> {
if (err) return next(err)
res.render('index', {
expedientes,
current: page,
pages: Math.ceil(count / perPage)
})
})
})
});
Second route:
router.get('/select/:id', async (req,res) => {
const { id } = req.params;
const {path} = req.path;
const expediente = await Expediente.findOne({selected:true});
const expediente2 = await Expediente.findById(id);
if (expediente){
expediente.selected=!expediente.selected;
await expediente.save();
}
expediente2.selected=!expediente2.selected;
await expediente2.save();
res.redirect('/1');
});
I need to use var page for the last line of the second route. How can i get that value to use it? I have try:
res.redirect('/'+ page);
but it doesn't work.
Some information about this problem?
You should probably avoid keeping the front-end state in your backend. In order to avoid this you can introduce a query parameter with the redirect
router.get('/select/:id', async ( req, res ) => {
const { id } = req.params;
const {path} = req.path;
const expediente = await Expediente.findOne({selected:true});
const expediente2 = await Expediente.findById(id);
const page = req.query.page;
if (expediente){
expediente.selected=!expediente.selected;
await expediente.save();
}
expediente2.selected=!expediente2.selected;
await expediente2.save();
res.redirect('/' + page);
});
Then you should call the URL with /select/2?page=1 which will redirect you to the page.
In your solution when you have two routes called by two different clients will have the very same page being defined, which is not what you probably want.
The solution is not easy. In my app I have two different needs. Both, need to obtain data from the database.
The first is pagination, each time a pagination-button is pressed, the database must be consulted to obtain the set of records for that page-pagination.
The second is the record-selection, it also need the database because when you select a record the id is stored so that the delete and edit buttons can act. All this works perfectly.
But the problem is when I select a record and also I advance in the pagination and select again other record. when the page is loaded again, I can not find a way to redirect back to the page from which it came. That is the page-pagination where the second record was selected.
So I thought that saving the last page in a variable, I could use that variable to redirect back in other route.
My question therefore is: can I save res.params.page in a variable and then use that varible in a different route?
I have a form that I'm trying to use to update a React component. The state seems to be altered because it re-renders no problem when I hit submit, but the data doesn't persist. More importantly, I get a 404 saying that the json object I'm trying to change can't be found.
I think it's something to do with how I'm trying to use my axios request in my actions, but I wouldn't be surprised if I'm off base from looking at this for too long.
Here are the relevant tidbits
Routes
const User = db.model('user')
const router = require('express').Router();
router.put('/:id', (req, res, next) => {
User.findById(req.params.id)
.then(user => user.update(req.body))
.then(updated => res.status(201).json(updated))
.catch(next)
})
Actions
const editUser = (userId, userInfo) => ({
type: UPDATE_USER,
userId,
userInfo
})
export const updateUser = function(id, info) {
return dispatch => {
dispatch(editUser(id, info))
axios.put(`/api/users/${id}`, {info})
.catch(err => console.error("Wasn't able to update user.", err))
}
}
Here are the errors I'm getting
POST http://localhost:1337/api/users/1 Wasn't able to update property. 404 (Not Found)
Error: Request failed with status code 404
at createError (createError.js:15)
at settle (settle.js:18)
at XMLHttpRequest.handleLoad (xhr.js:77)
And that URI totally exists, so I don't really see why the request seems to think otherwise.
Any help is appreciated!
Figured it out. Needed an extra line using .then to actually send the information to the my db.
export const updateUser = function(id, info) {
return dispatch => {
dispatch(editUser(id, info))
axios.put(`/api/forSale/${id}`, info)
.then(res => res.data)
.catch(err => console.error("Wasn't able to update property.", err))
}
}