Salut et bienvenue sur le forum,
Ce que tu essaies de faire, c'est de cumuler php, nodejs et ça n'a pas de sens. Soit tu développes en php, soit tu développes en JS backend. Faire de « l'hybride » est contre productif. Pourquoi ?
- tu auras inévitablement des problèmes de performance, À chaque visiteur sur app.js, Node doit ouvrir un processus shell (ouch la sécurité

), charger PHP, interpréter le fichier, puis fermer le processus. Sur 100 utilisateurs simultanés, le serveur va saturer son CPU juste pour "lancer" PHP
- de maintenabilité,
- de sécurité, etc.
- tu recrées ostensiblement la même roue pour rien.
En outre, on peut facilement réaliser ce que tu souhaites.
index.php
<?php
$name = "Chuck Norris";
$description = "<p>Il n'existe ni hommes dangereux, ni armes dangereuses. Seulement Chuck Norris. Quand Chuck Norris joue a SimCity, il n'y a jamais d'émeutes. Chuck Norris peut chanter « Papillon de lumiere » sans se faire huer . Quand Skynet a provoqué le jugement dernier, Chuck Norris a fait appel... On ne demande pas à Chuck Norris si sa grand-mère fait du vélo. Sa grand-mère FAIT du vélo. Dieu dit « Tu ne convoiteras point la maison de ton prochain; tu ne convoiteras point la femme de ton prochain... »; et Chuck répondit:« Trop tard pour toi ». Une distance de 100km à vol d'oiseau correspond à 50km à dos de Chuck Norris. Chuck Norris est plus nombreux que les Chinois. C'est Chuck Norris qui traverse la route, une voiture arrive et ... paf la voiture! Chuck Norris peut faire des bonhommes de neige avec de l'eau liquide.</p>";
?>
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>express-php</title>
</head>
<body>
<div id="app">
<h1><?= $name ?></h1>
<?= $description ?>
</div>
</body>
</html>
app.js
import { exec } from 'node:child_process'
import express from 'express'
const app = express()
const port = 3000
app.get('/', (req, res) => {
exec('php index.php', (error, stdout, stderr) => {
if (error) {
console.error(`Erreur PHP: ${error.message}`)
return res.status(500).send("Internal server error")
}
return res.send(stdout)
})
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Si tu aimes la syntaxe de PHP pour le HTML mais que tu veux rester sur Node, regarde du côté de EJS (npm install ejs). C'est presque la même chose (<%= name %>), mais c'est intégré nativement, sécurisé et performant. Par contre, tu n'auras pas les mêmes fonctionnalités qu'offrent php.
Si tu souhaites un moteur de template ultra léger, plus complet et nettement plus performant, tu peux utiliser le mien.
https://www.npmjs.com/package/@niuxe/template-engine
Pour l'utiliser avec Express (démo ultra simple):
l'arborescence des fichiers :
server.js
import fs from 'node:fs/promises'
import path from 'node:path'
import express from 'express'
import { TemplateEngine } from '@niuxe/template-engine'
import { AsyncPlugin, PartialsPlugin, LayoutPlugin, HelpersPlugin } from '@niuxe/template-engine/plugins'
const app = express()
const engine = new TemplateEngine()
engine.use(AsyncPlugin)
engine.use(PartialsPlugin)
engine.use(LayoutPlugin)
engine.use(HelpersPlugin)
// Helpers
engine.helper('upper', str => str.toUpperCase())
engine.helper('now', () => new Date().toLocaleDateString())
async function bootstrapTemplates() {
const partialsDir = './views/partials'
const layoutsDir = './views/layouts'
try {
const [partialFiles, layoutFiles] = await Promise.all([
fs.readdir(partialsDir).catch(() => []),
fs.readdir(layoutsDir).catch(() => [])
])
for (const file of partialFiles) {
const content = await fs.readFile(path.join(partialsDir, file), 'utf8')
engine.partial(path.parse(file).name, content)
}
for (const file of layoutFiles) {
const content = await fs.readFile(path.join(layoutsDir, file), 'utf8')
engine.layout(path.parse(file).name, content)
}
console.log('Templates (Partials & Layouts) chargés')
} catch (err) {
console.error('Erreur chargement:', err)
}
}
// --- CONFIGURATION EXPRESS ---
app.engine('html', async (filePath, options, callback) => {
try {
const content = await fs.readFile(filePath, 'utf8')
// Le moteur utilise les layouts/partials déjà chargés en mémoire via bootstrapTemplates
const html = engine.render(content, options)
return callback(null, html)
} catch (err) {
return callback(err)
}
})
app.set('views', './views')
app.set('view engine', 'html')
// --- ROUTES ---
app.get('/', (req, res) => {
res.render('index', {
title: 'Accueil Ultra-Fast',
user: 'Patrice',
items: ['Vitesse', 'Légèreté', 'Simplicité']
})
})
// --- LANCEMENT ---
async function startServer() {
await bootstrapTemplates()
app.listen(3000, () => {
console.log('Serveur prêt sur http://localhost:3000')
})
}
startServer()
main.html :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
[[> header ]]
<main>[[block:content]][[/block]]</main>
<footer>par @niuxe - [[= helpers.now() ]]</footer>
</body>
</html>
header.html
<header>
<h1>[[= title ]]</h1>
<nav>Menu</nav>
</header>
index.html
[[extends:main]]
[[block:content]]
<h2>Bienvenue [[= helpers.upper(user) ]] !</h2>
<p>Pourquoi utiliser ce moteur ?</p>
<ul>
[[ items.forEach(item => { ]]
<li><strong>[[= item ]]</strong></li>
[[ }) ]]
</ul>
[[ if (items.length > 2) { ]]
<p>C'est une liste complète !</p>
[[ } ]]
[[/block]]
Modifié par Niuxe (19 Feb 2026 - 15:41)