I've created a REST API in Node.js with the PostgresSQL using its pg library and express for REST API creation and routing and body-parser for json. The REST API functions properly, I've tested it on the thunder client of vscode. Here's the middle ware code:
const express = require('express');
const bodyParser = require('body-parser');
const foodRecipeRoute = require('../router/foodRecipe.router');
const path = require('path');
let app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use("/foodRecipe", foodRecipeRoute);
const port = 8080 || 8000;
// Connecting port
app.listen(port, () => { console.log('Port connected to: ' + port) });
// Find 404 and hand over to error handler
app.use((req, res, next) => { next(console.log("Error occurred")); });
// Index Route
app.get('/', (req, res) => { res.send('invalid endpoint'); });
// error handler
app.use(function(err, req, res, next) { console.error(err.message);
if (!err.statusCode) err.statusCode = 500;
res.status(err.statusCode).send(err.message);
});
// Static build location
app.use(express.static(path.join(__dirname, 'dist')));
The code of the tasks to be performed in foodDb.tasks.js
is as under:
let PostgreSQL = require('pg');
const Pool = PostgreSQL.Pool;
let pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'postgres',
password: '6equj5*243',
port: 5432,
});
/**
* @param {Request} req
* @param {Response} res
*/
const hasAppUser = (req, res) => {
const { appusername, userpassword } = req.body;
pool.query("select exists(select from appuser where appusername = $1 and userpassword = $2);",
[appusername, userpassword],
(error, result) => {
if (error) throw error;
res.status(200).json(result.rows[0]);
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const signInUser = (req, res) => {
const { appusername, userpassword } = req.body;
pool.query(
"select username, appusername" +
" from appuser" +
" where appusername = $1" +
" and userpassword = $2;",
[appusername, userpassword],
(error, result) => {
if (error) {
throw error;
}
if (result.rows.length == 0) {
res.status(404).json({signedIn: false});
}
res.status(200).json(result.rows[0]);
}
);
};
/**
* @param {Request} req
* @param {Response} res
*/
const signUpUser = (req, res) => {
const { username, appusername, userpassword } = req.body;
pool.query(
"insert into appuser (" +
" userId," +
" username," +
" userpassword," +
" appusername" +
") values (" +
" uuid_generate_v4()," +
" $1," +
" $2," +
" $3" +
");",
[username, userpassword, appusername],
(error, _) => {
if (error) {
res.status(400).json({ err: error });
}
res.status(201).json({ message: `Created ${appusername} successfully` });
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const createRecipe = (req, res) => {
const { recipename, ingredientnames, ingredientamounts, appusername } = req.body;
for (let i = 0; i < ingredientnames.length; i += 1){
pool.query(
"insert into recipedetails(detailID, recipeid, ingredientid, ingredientamount, appusername)" +
" values(uuid_generate_v4(), fetch_recipe_id($1), get_ingredient_id($2), $3, $4);",
[recipename, ingredientnames[i], ingredientamounts[i], appusername],
(error, _) => {
if (error) throw error;
res.status(201).send(
`Created ${recipename} with ${ingredientnames[ingredientnames.length - 1]}' +
'as last ingredient by ${appusername}`
);
}
);
}
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const addIngredientToRecipe = (req, res) => {
const { recipename, ingredientname, ingredientamount, appusername } = req.body;
pool.query(
"Insert into recipedetails(detailId, recipeid, ingredientid, ingredientamount, appusername)" +
"values(uuid_generate_v4(), get_user_recipe_id($1, $4), get_ingredient_id($2), $3, $4);",
[recipename, ingredientname, ingredientamount, appusername],
(err, res) => {
if (err) throw err;
res.status(201).send("Insertion successful");
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const getUserRecipies = (req, res) => {
const { appusername } = req.body;
pool.query(
"select recipename" +
" from recipe" +
" inner join (" +
" select recipeid" +
" from recipedetails" +
" where appusername = $1" +
") as userRecipies" +
" on recipe.recipeid = userRecipies.recipeid" +
" group by recipename",
[appusername],
(err, result) => {
if (err) throw err;
let userAllRecipies = [];
for (let i = 0; i < result.rows.length; i += 1) {
userAllRecipies.push(result.rows[i].recipename);
}
res.status(200).json({recipies: userAllRecipies});
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const getUserRecipeDetails = (req, res) => {
const { appusername, recipename } = req.body;
pool.query(
"select ingredientname, ingredientamount" +
" from ingredient" +
" inner join (" +
" select ingredientid, ingredientamount" +
" from recipedetailedview" +
" where recipedetailedview.appusername = $1" +
" and recipedetailedview.recipename = $2" +
" group by ingredientid, ingredientamount" +
") as userRecipeDetails " +
"on ingredient.ingredientid = userRecipeDetails.ingredientid;",
[appusername, recipename],
(err, result) => {
if (err) throw err;
res.status(200).json({ recipeDetails: result.rows });
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const updateUserRecipeIngredientName = (req, res) => {
const {
appusername,
oldingredientname,
newingredientname,
recipename
} = req.body;
pool.query(
"update recipedetails" +
" set ingredientid = get_ingredient_id($3)" +
" where recipeid = get_user_recipe_id($1, $4)" +
" and ingredientid = $2;",
[appusername, oldingredientname, newingredientname, recipename],
(err, _) => {
if (err) throw err;
res.status(201).send(`Updated with ${newingredientname}`);
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const updateUserRecipeIngredientAmount = (req, res) => {
const {
appusername,
recipename,
ingredientname,
ingredientamount
} = req.body;
pool.query(
"update recipedetails set ingredientamount = $4" +
" where recipeid = get_user_recipe_id($1, $2)" +
" and ingredientid = get_ingredient_id($3);",
[appusername, recipename, ingredientname, ingredientamount],
(err, _) => {
if (err) throw err;
res.status(201).send(`Updated ingredient amount with: ${ingredientamount}`);
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const updateUserRecipeName = (req, res) => {
const { appusername, recipename, oldrecipename, newrecipename } = req.body;
pool.query(
"update recipedetails set recipeid = get_recipe_id($3)" +
" where recipeid = get_user_recipe_id($1, $2) and appusername = $1;",
[appusername, recipename, oldrecipename, newrecipename],
(err, _) => {
if (err) throw err;
res.status(201).send("Updated user recipe name");
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const deleteUserRecipeIngredient = (req, res) => {
const { appusername, recipename, ingredientname } = req.body;
pool.query(
"delete from recipedetails where recipeid = get_user_recipe_id($1, $2)" +
" and ingredientid = get_ingredient_id($3);",
[appusername, recipename, ingredientname],
(err, _) => {
if (err) throw err;
res.status(201).send(`deleted ${recipename}'s ingredient ${ingredientname}`);
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const deleteUserRecipeName = (req, res) => {
const { appusername, recipename } = req.body;
pool.query(
"delete from recipedetails where recipeid = fetch_recipe_id($2) and appusername = $1;",
[appusername, recipename],
(err, _) => {
if (err) throw err;
res.status(201).send(`deleted ${recipename} of appuser ${appusername}`);
}
);
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const deleteUserAllRecipies = (req, res) => {
const { appusername } = req.body;
pool.query("Delete from recipedetails where appusername = $1;",
[appusername],
(err, _) => {
if (err) throw err;
res.status(201).send(`Deleted ${appusername} recipies`);
});
};
/**
*
* @param {Request} req
* @param {Response} res
*/
const deleteUser = (req, res) => {
const { appusername } = req.body;
pool.query("Delete from appuser where appusername = $1;",
[appusername],
(err, _) => {
if (err) throw err;
res.status(201).send(`Deleted ${appusername}`);
});
};
module.exports = {
hasAppUser,
signInUser,
signUpUser,
createRecipe,
addIngredientToRecipe,
getUserRecipies,
getUserRecipeDetails,
updateUserRecipeIngredientName,
updateUserRecipeIngredientAmount,
updateUserRecipeName,
deleteUserRecipeIngredient,
deleteUserRecipeName,
deleteUserAllRecipies,
deleteUser
};
The routes code is as under as well, I've used POST
method in the route for most of the fetching operations that's because in flutter's http GET
method you've to pass the params
in the url/uri of the API, which is quite unsafe, so that's why I went for the POST
method so that I may securely make fetching on the flutter side as well which will definitely expose info to malicious intent holders online, when deployed.
The routes code involves the usage of the API tasks code, which I've named as foodDB.tasks.js
file. I've already pasted this file's code above. The routes are defined as:
let express = require('express');
let tasks = require('../tasks/foodDb.tasks');
let foodRecipeRoute = express.Router();
// The endpoint to be consumed by client to
// determine whether the API url is working or not
foodRecipeRoute.get("/", (req, res) => {
res.status(200).json({hasData: "has data"});
});
foodRecipeRoute.post("/checkUser", tasks.hasAppUser);
foodRecipeRoute.post("/signIn", tasks.signInUser);
foodRecipeRoute.post("/signUp", tasks.signUpUser);
foodRecipeRoute.post("/getRecipies", tasks.getUserRecipies);
foodRecipeRoute.post("/getRecipeDetails", tasks.getUserRecipeDetails);
foodRecipeRoute.post("/createRecipe", tasks.createRecipe);
foodRecipeRoute.post("/addIngredient", tasks.addIngredientToRecipe);
foodRecipeRoute.put("/ingredientNewname", tasks.updateUserRecipeIngredientName);
foodRecipeRoute.put("/ingredientNewAmount", tasks.updateUserRecipeIngredientAmount);
foodRecipeRoute.put("/recipeNewname", tasks.updateUserRecipeName);
foodRecipeRoute.delete("/deleteIngredient", tasks.deleteUserRecipeIngredient);
foodRecipeRoute.delete("/deleteRecipe", tasks.deleteUserRecipeName);
foodRecipeRoute.delete("/deleteRecipies", tasks.deleteUserAllRecipies);
foodRecipeRoute.delete("/deleteUser", tasks.deleteUser);
module.exports = foodRecipeRoute;
For the client (flutter) side of my project, I m first determining whether the API url is fully functional, if so then it can navigate the screen of the concern. The code is in main.dart file, which is as under:
// ignore_for_file: avoid_print
import 'dart:convert';
import 'package:flutter/material.dart';
import "package:http/http.dart" as http;
import 'package:provider/provider.dart';
import 'core/config/api_url.dart';
import 'view/viewmodels/recipies_screen_viewmodel.dart';
import 'view/screens/user_recipies_screens.dart';
import 'view/viewmodels/recipe_ingredient_screen_viewmodel.dart';
void main() {
runApp(
MultiProvider(
providers: [
ListenableProvider<RecipeIngredientScreenViewmodel>(
create: (context) => RecipeIngredientScreenViewmodel(),
),
ListenableProvider<RecipiesScreenViewmodel>(
create: (context) => RecipiesScreenViewmodel(),
),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Future getUrlData() async {
var response = await http.get(Uri.parse(apiUrl));
debugPrint(response.statusCode.toString());
return json.decode(response.body);
}
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FutureBuilder(
future: getUrlData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return const UserRecipiesScreen();
}
return Center(
child: ListView(
children: <Widget>[
ElevatedButton(
onPressed: () async {
var res = await getUrlData();
debugPrint(res.toString());
},
child: const Text("Press me"),
),
],
),
);
},
),
);
}
}
The method getUrlData
gets the data from the "/"
endpoint of the router, foodRecipeRouter
in my case. The code is mentioned the routing of endpoints code above, while that of this endpoint is as under:
foodRecipeRoute.get("/", (req, res) => {
res.status(200).json({hasData: "has data"});
});
It's result is as under using thunder client of vscode:
The result of building this app on web was this:
When I click the button, I get:
Flutter Web Bootstrap: Programmatic
The Flutter DevTools debugger and profiler on Chrome is available at:
http://127.0.0.1:9101?uri=http://127.0.0.1:51942/MhxrvaKN7aU=
Error: XMLHttpRequest error.
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 299:10 createErrorWithStack
dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 341:28 _throw
dart-sdk/lib/core/errors.dart 116:5 throwWithStackTrace
dart-sdk/lib/async/zone.dart 1378:11 callback
dart-sdk/lib/async/schedule_microtask.dart 40:11 _microtaskLoop
dart-sdk/lib/async/schedule_microtask.dart 49:5 _startMicrotaskLoop
This has confused me due to the fact that I m making a local REST API, so there's no question of the need of the headers, correct me if I m wrong and help me please regarding this issue. And also please guide me that how to connect a localhost api to a flutter app?
I tried to change my apiUrl
from http://localhost:8080/foodRecipe/
to http://192.168.100.4:8080/foodRecipe/
for thinking that localhost
is not understood by flutter but all in vain. I make sure that there're no headers
checked as required from thunder client.
I refer to this answer but doesn't work, no data was fetched by GET
operation on the flutter side.