Encountered a strange issue while using delete method in Express app.
Here is my app.js document. I am using elevatorRouter for "/elevators" routes.
app.js
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/passwordgenerator", passwordgeneratorRouter);
app.use("/elevators", elevatorRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;
Here is my route file. I am including the elevator router from app.js. It seems that edit route is working fine. I have only issue with "delete" method.
elevators.js
const express = require("express");
const router = express.Router();
const Elevator = require("../models/elevator");
const middleware = require("../middleware");
// Edit Elevator Route
router.get("/:id/edit", (req, res) => {
Elevator.findById(req.params.id, (err, foundElevator) => {
res.render("elevators/edit", { elevator: foundElevator });
});
});
// Delete Elevator Route
router.delete("/:id", (req, res) => {
Elevator.findByIdAndRemove(req.params.id, (err) => {
if (err) {
res.redirect("/elevators");
} else {
res.redirect("/elevators");
}
});
});
Here is my views. I didnt completely post the entire html. Instead, I have copied partially. I beleive this will be enough. I used postman to send "POST" method directly but still receiving the same 404 error. I beleive it is not related to the view
view
<div class="col-md-9">
<div class="card">
<img src="<%= elevator.image %>" class="card-img-top" alt="..." />
<div class="card-body">
<h4 class="card-title"><%= elevator.projectName%></h4>
<p><%= elevator.projectNumber %></p>
<% if(user) { %>
<form
class="delete-form"
action="/elevators/<%= elevator._id %>?_method=DELETE"
method="post"
>
<button class="btn btn-danger">Delete</button>
</form>
<% } %>
</div>
<div class="card-body">
Back
</div>
</div>
</div>
You cannot get response from a DELETE route with a POST HTTP request. It is a well-known issue of browsers, but HTML forms can only send FormData via POST and DELETE is not compatible.
Therefore, Express routing does not match and says POST:“/:id” route does not exist.
Try changing .delete to .post and it will work.
Edit: There is a method-overwrite module to convert POST requests to DELETE via a query param _method (or any other name you choose).
Related
Updated on 1/18/2023 to reflect the answer from Bijan
Facing problem
I want to convert a web application created with Rails 7 that uses importmap into a PWA.
However, I'm having trouble registering the service-worker using importmap.
I'd appreciate it if you could give me some advice.
Status quo
At first, I tried to use this gem, but it didn't work in the importmap environment, so I referred to this site, but it didn't work.
The result of lighthouse check is as below.
Development environment
ruby 3.1.3
rails 7.0.3
importmap-rails 1.1.2
tailwindcss-rails 2.0.10
Sprockets 4.1.1.
Related source code
config/route.rb
get '/service_worker', to: 'service_workers#service_worker'
get '/offline', to: 'service_workers#offline'
./app/controllers
├── service_workers_controller.rb
class ServiceWorkerController < ApplicationController
protect_from_forgery except: :service_worker
skip_before_action :require_login
def service_worker; end
def offline; end
end
./app/views/service_worker
├── offline.html.erb
└── service-worker.html.erb
<script>
const VERSION = 'v1';
const NAME = 'app_name-';
const CACHE_NAME = NAME + VERSION;
const urlsToCache = [
"./offline.html"
];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function (event)
if (event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin')
return;
event.respondWith(
cache.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(
cache.keys().then(keys => Promise.all(
keys.map(key => {
if (!CACHE_NAME.includes(key)) {
return cache.delete(key);
}
})
)).then(() => {
console.log(CACHE_NAME + "activated");
})
);
});
</script>
/app/javascript/controllers
└── service-worker_controller.js
import { Controller } from "#hotwired/stimulus"
export default class extends Controller {
connect () {
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register("/service-worker", {scope: "/" }).then(function (registration) {
console.log("ServiceWorker registration successful with scope: ", registration.scope);
}, function (err) {
console.log("ServiceWorker registration failed: ", err);
});
});
}
}
}
app/views/layouts/
└── application.html.erb
<!DOCTYPE html>
<html lang="ja">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
<title><%= page_title(yield(:title)) %></title>
<%= render 'application/favicon' %>
<%= display_meta_tags(default_meta_tags) %>
</head>
<body class="dark:bg-gray-800 break-words flex flex-col min-h-screen">
<%= render 'static_pages/navbar' %>
<div id="flash" class="z-50 flex-col fixed top-16 right-1 ">
<%= render 'shared/flash' %>
</div>
<%= tag.main class: "mb-auto relative" do %>
<div class="w-full" data-controller="service-worker">
<%= yield %>
</div>
<% end %>
<%= render 'static_pages/footer' %>
</body>
</html>
Error message
when I reload the page, The following error appears on the console of chrome DV tool.
The script has an unsupported MIME type ('text/html').
Also, the following message appears on chrome DV tool console.
Please let me know if you find anything strange.
So far your example seems fine except the rendering of your service-worker.js file via the controller. Your controller complains that it didn’t find a template. You could create a view for the service_worker method and paste the content of your service-worker.js file inside. You could also make your controller method send the file down directly. Should be an easy fix.
I'm trying to retrieve my email messages from an Outlook account through Javascript.
I use ADAL to authenticate myself and that seems to work. However if I try to use the returned token in a GET request to the outlook REST API I get an unauthorised error back with following additional info in the header:
x-ms-diagnostics:2000001;reason="This token profile 'V1IdToken' is not applicable for the current protocol.";error_category="invalid_token"
My request looks like:
function test2(token) {
try
{
var xhr = new XMLHttpRequest();
xhr.open("GET", 'https://outlook.office.com/api/v1.0/me/messages');
// The APIs require an OAuth access token in the Authorization header, formatted like this: 'Authorization: Bearer <token>'.
xhr.setRequestHeader("Authorization", "Bearer " + token);
// Process the response from the API.
xhr.onload = function () {
// ...
}
// Make request.
xhr.send();
I both tried v1.0 and v2.0 REST api.
Based on the error message you were request the Outlook online REST using the id_token. The id_token is only used for the client to authenticate users. To request the Outlook online REST, we need to use the access token.
And if you were developing with angularJS, there is no need to append the token manually. Here is a code sample for your reference:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.13/js/adal.min.js"></script>
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.13/js/adal-angular.min.js"></script>
<script src="//unpkg.com/angular-ui-router/release/angular-ui-router.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.js"></script>
<div ng-app="myApp">
<div> Hello {{ userInfo.userName }}</div>
<div ng-controller="homeCtrl">
<ul class="nav navbar-nav navbar-right">
<li><a ng-click="getMessages()">Get Messages</a></li>
<li><a class="btn btn-link" ng-show="userInfo.isAuthenticated" ng-click="logout()">Logout</a></li>
<li><a class="btn btn-link" ng-hide="userInfo.isAuthenticated" ng-click="login()">Login</a></li>
</ul>
<div>
<table class="table table-striped">
<tbody>
<tr data-ng-repeat="item in messages">
<td>
<p>{{item.Subject}}</p>
</td>
<td>
<p>{{item.Sender.EmailAddress.Address}}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script>
var myApp = angular.module('myApp', ['AdalAngular'])
.config(['$httpProvider', 'adalAuthenticationServiceProvider', function ($httpProvider, adalProvider) {
var endpoints = {
"https://outlook.office.com": "https://outlook.office.com",
};
adalProvider.init(
{
instance: 'https://login.microsoftonline.com/',
tenant: '{yourTenant}.onmicrosoft.com',
clientId: '{yourAppId}',
extraQueryParameter: 'nux=1',
endpoints: endpoints,
},
$httpProvider
);
}])
myApp.controller('homeCtrl', ['$scope', '$http', 'adalAuthenticationService', '$location', 'toGetMessagesSvc', function ($scope, $http, adalService, $location, toGetMessagesSvc) {
$scope.login = function () {
adalService.login();
};
$scope.logout = function () {
adalService.logOut();
};
$scope.getMessages = function () {
toGetMessagesSvc.getMessages().success(function (results) {
$scope.messages = results.value;
$scope.loadingMessage = "";
});
}
}]);
myApp.factory('toGetMessagesSvc', ['$http', function ($http) {
var apiEndpoint = "https://outlook.office.com/";
$http.defaults.useXDomain = true;
delete $http.defaults.headers.common['X-Requested-With'];
return {
getMessages: function () {
return $http.get(apiEndpoint + 'api/v1.0/me/messages');
}
};
}]);
</script>
Despite following the exact instructions of this video: https://www.youtube.com/watch?v=pNKNYLv2BpQ and changing details where necessary (e.g. I am using localhost:8888 as oppose to 3000), I am always getting error messages, no matter what I try. It is becoming extremely frustrating as I can't, for the life of me, figure out where I am going wrong.
I am currently getting the error message GET http://localhost:8888/socket.io/1/?t=1462670368869 404 (Not Found)after a massive struggle of continuously getting an error where it couldn't locate the 'socket.io.js' file. Can anyone shed some light on this devastatingly cryptic topic?
HTML:
<html>
<head>
<title>Chat with socket.io and node.js</title>
<style>
#chat{
height:500px;
}
</style>
</head>
<body>
<div id="chat"></div>
<form id="send-message">
<input size="35" id="message"></input>
<input type="submit"></input>
</form>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script src="socket.io.js"></script>
<script>
jQuery(function($){
var socket = io.connect();
var $messageForm = $('#send-message');
var $messageBox = $('#message');
var $chat = $('#chat');
$messageForm.submit(function(e){
e.preventDefault();
socket.emit('send message', $messageBox.val());
$messageBox.val('');
});
socket.on('new message', function(data){
$chat.append(data + "<br/>");
});
});
</script>
</body>
</html>
JavaScript:
var express = require('express');
app = express();
server = require('http').createServer(app);
io = require('socket.io').listen(server);
server.listen(8888);
app.get('/', function(req, res){
res.sendfile(__dirname + '/index.html');
});
io.sockets.on('connection', function(socket){
socket.on('send message', function(data){
io.sockets.emit('new message', data);
});
});
JSON:
{
"name": "chat",
"version": "0.0.1",
"private": "true",
"dependencies": {
"express": "^4.10.2",
"socket.io": "^0.9.16"
}
}
I am super new to React and quite new to Meteor.
I am doing a Meteor.call to a function ('getTheThing'). That function is fetching some information and returns the information as a response. In my browser I can see that the method is returning the correct information (a string), but how do I get that response into the DOM?
(As you can see, I have tried to place it in the DOM with the use of ReactDOM.findDOMNode(this.refs.result).html(response);, but then I get this error in my console: Exception in delivering result of invoking 'getTheThing': TypeError: Cannot read property 'result' of undefined)
App = React.createClass({
findTheThing(event) {
event.preventDefault();
var username = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Meteor.call("getTheThing", username, function(error, response){
console.log(response);
ReactDOM.findDOMNode(this.refs.result).html(response);
});
ReactDOM.findDOMNode(this.refs.textInput).value = "";
},
render(){
return(
<div className="row">
<div className="col-xs-12">
<div className="landing-container">
<form className="username" onSubmit={this.findTheThing} >
<input
type="text"
ref="textInput"
placeholder="what's your username?"
/>
</form>
</div>
<div ref="result">
</div>
</div>
</div>
);
}
});
this is under the different context, thus does not contain the refs there. Also, you cannot set html for the Dom Element. You need to change into Jquery element
var _this = this;
Meteor.call("getTheThing", username, function(error, response){
console.log(response);
$(ReactDOM.findDOMNode(_this.refs.result)).html(response);
});
Though i recommend you to set the response into the state and let the component re-rendered
For a complete React way
App = React.createClass({
getInitialState() {
return { result: "" };
},
shouldComponentUpdate (nextProps: any, nextState: any): boolean {
return (nextState['result'] !== this.state['result']);
},
findTheThing(event) {
event.preventDefault();
var username = ReactDOM.findDOMNode(this.refs.textInput).value.trim();
Meteor.call("getTheThing", username, function(error, response){
console.log(response);
_this.setState({ result: response });
});
ReactDOM.findDOMNode(this.refs.textInput).value = "";
},
render(){
return(
<div className="row">
<div className="col-xs-12">
<div className="landing-container">
<form className="username" onSubmit={this.findTheThing} >
<input
type="text"
ref="textInput"
placeholder="what's your username?"
/>
</form>
</div>
<div ref="result">{this.state['result']}</div>
</div>
</div>
</div>
);
}
});
I am a beginner here. I currently am working with twitter bootstrap (html,css,js), node.js for server side of things, mongodb for no-sql dbs. I have been looking at a bunch of things and am looking for an answer or any direction for how to go about having a form i.e. a user account registration to be filled out on the html side (already have this done)
<code>
<form class="validateForm" id="registerform" method="POST" action="" accept-charset='UTF-8'>
<fieldset>
<legend>Register</legend> <br>
<input type="text" name="firstName" id="firstName" placeholder="First Name" maxlength="20" value=""/> <br>
<input type="text" name="lastName" id="lastName" placeholder="Last Name" maxlength="20" value=""/> <br>
<input type="text" name="email" id="email" placeholder="Email" maxlength="30" value=""/> <br>
<input type="password" name="password" id="password" placeholder="Password" value=""/> <br>
<input type="password" name="confirmPassword" id="confirmPassword"placeholder="Confirm Password" value=""/> <br>
<input type="text" name="phoneNumber" id="phoneNumber" placeholder="Phone Number" maxlength="10" value=""/> <br>
<input type="date" name="birthday" id="birthday" placeholder="Birthday" value=""/>
<br>
<label id="legalConfirm" for="agree"><input type="hidden" name="agree" value="0" />
<input type="checkbox" name="agree" id="agree" value="1" checked="checked" /> By clicking join you confirm that you accept our Privacy Policy and Terms of Service.</label>
<input class="btn btn-primary" type="submit" name="create" value="Join"/>
<button type="button" class="btn">Cancel</button>
</fieldset>
</form>
That is my form using twitter bootstrap and validating it on the js side. However, not sure what is support to go in the action.
The page is being served by node.js through my router.
<code>
exports.signUp = function(req, res){
res.render('signUp');
};
That is located in the routes folder and being called as a require('./routes/home.js) on my server.js file.
I have mongoose as a dependency in the package.json file and have tried to find more documentation on it which is pretty confusing.
My view engine is using html (not jade or ejs) although I am willing to use either, they are pretty easy to understand.
I am running mongo straight from my computer but was looking into mongohq to make things a bit easier.
Basically if anyone can help or give me direction for how to go about completing the form so the information can be put (POST) into a db in mongo and then pulled out of mongo (GET) and served onto a page as a user profile.
Another thing that I might need clarification on is how to go about GET-ting it to show on a new page such as My Profile Page (do I have to create a template/page or how do I link the info saved for creating an account to a My Profile page).
A guide through a whole form process from client-side to server-side including POST(the form) and GET(to a new page), using HTML, JS (jQuery, JSON), Node.Js, and Mongodb.
UPDATE
So here I have tried this for the POST, so now it is working, but getting an can't find html module error.
Here is my code for my server.js file.
<code>
var express = require('express')
, home = require('./routes/home.js')
, path = require('path')
, http = require('http')
, mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
app.post('/signUp', home.signUpUser);
---- Now this is my home.js (aka router js file)
<code>
var mongoose = require('mongoose');
var conn = mongoose.connection;
exports.index = function(req, res){
res.render('index');
};
exports.signUp = function(req, res){
res.render('signUp');
};
exports.about = function(req, res) {
res.render('about');
};
exports.signUpUser = function (req, res, next) {
if (req.body && req.body.email && req.body.password === req.body.confirmPassword) {
var obj = {
firstName: req.body.firstName || 'na',
lastName: req.body.lastName || 'na',
email: req.body.email,
password: req.body.password,
phone: req.body.phoneNumber || '555-555-555',
birthday: new Date(req.body.birthday) || new Date()
};
conn.collection('users').insert(obj, function (err) {
if (!err) {
res.redirect('/about.html');
} else {
next(err);
}
});
} else {
next(new Error('Incorrect POST'));
}
};
In your form the "action" is the url the request should be made so for example action="/signup".
I'm presuming you're using express in you example (from the res.render call).
With express you need to add the bodyParser middleware to your middlewarestack with:
app.use(express.bodyParser());
After this you need a route for the post:
app.post('/signup', routes.postedSignup);
The routes.postedSignup could look something like this:
exports.postedSignup = function (req, res, next) {
if (req.body && req.body.email && req.body.password === req.body.confirmPassword) {
var obj = {
firstName: req.body.firstName || 'na',
lastName: req.body.lastName || 'na',
email: req.body.email,
password: bcrypt.hashSync(req.body.password, 8),
phone: req.body.phoneNumber || '555-555-555',
birthday: new Date(req.body.birthday) || new Date()
};
db.userCollection.insert(obj, function (err) {
if (!err) {
res.redirect('/login');
} else {
next(err);
}
});
} else {
next(new Error('Incorrect POST'));
}
};
Here the db should be your database driver and bcrypt is the bcrypt module for hashing the passwords with a random salt.
To get this data from your db, you'd create a route for it:
app.get('/profile/:email', routes.getProfile);
This route would then look something like this (it's presuming the user is logged in and there's a valid session and the users data saved as req.session.user):
exports.getProfile = function (req, res, next) {
if (req.session.user && req.session.user.email === req.params.email || req.session.user.admin) {
db.userCollection.findOne({ email: req.params.email }, function (err, result) {
if (!err && result) {
res.locals.user = result;
res.render('userprofile');
} else {
var errorMessage = err || new Error('no user found');
next(errorMessage);
}
});
} else {
next(new Error('Insufficient permissions'));
}
};
For the data from res.locals.user to be usable, you need to use some kind of template to inject the data on res.render. Any values set to res.locals (or passed into the render call as an object), will be usable in the template, in this case the var you'd access would be called user.
The next() calls deliver the errors to the errorhandler, which i'm presuming you have in your middlewares as the last one. If not, you should read about it here.
You could also handle the situation with a client-side template, so you'd only return json from your routes, and not render any templates. For that you'd just replace res.render with res.json and the template name with the object you wish to send as json.
For a real-world application you'd have to flesh this out some more, for example checking that there's no user already with the same email etc. The signup could be separated into couple different functions so the other function could be reused for editing user profiles.
Also include express.csrf to your middlewares to protect the users from csrf attacks.
Hopefully this will get you going.